Skip to main content

solana_vote_program/
vote_processor.rs

1//! Vote program processor
2
3use {
4    crate::vote_state::{self, handler::VoteStateTargetVersion},
5    log::*,
6    solana_instruction_error::InstructionError,
7    solana_packet::PACKET_DATA_WINCODE_CONFIG,
8    solana_program_runtime::{
9        declare_process_instruction, invoke_context::InvokeContext,
10        sysvar_cache::get_sysvar_with_account_check,
11    },
12    solana_pubkey::Pubkey,
13    solana_transaction_context::{
14        instruction::InstructionContext, instruction_accounts::BorrowedInstructionAccount,
15    },
16    solana_vote_interface::{instruction::VoteInstruction, program::id, state::VoteAuthorize},
17    std::collections::HashSet,
18};
19
20#[allow(clippy::too_many_arguments)]
21fn process_authorize_with_seed_instruction<F>(
22    invoke_context: &InvokeContext,
23    instruction_context: &InstructionContext,
24    vote_account: &mut BorrowedInstructionAccount,
25    target_version: VoteStateTargetVersion,
26    new_authority: &Pubkey,
27    authorization_type: VoteAuthorize,
28    current_authority_derived_key_owner: &Pubkey,
29    current_authority_derived_key_seed: &str,
30    is_vote_authorize_with_bls_enabled: bool,
31    consume_pop_compute_units: F,
32) -> Result<(), InstructionError>
33where
34    F: FnOnce() -> Result<(), InstructionError>,
35{
36    let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 1)?;
37    let mut expected_authority_keys: HashSet<Pubkey> = HashSet::default();
38    if instruction_context.is_instruction_account_signer(2)? {
39        let base_pubkey = instruction_context.get_key_of_instruction_account(2)?;
40        // The conversion from `PubkeyError` to `InstructionError` through
41        // num-traits is incorrect, but it's the existing behavior.
42        expected_authority_keys.insert(
43            Pubkey::create_with_seed(
44                base_pubkey,
45                current_authority_derived_key_seed,
46                current_authority_derived_key_owner,
47            )
48            .map_err(|e| e as u64)?,
49        );
50    };
51    vote_state::authorize(
52        vote_account,
53        target_version,
54        new_authority,
55        authorization_type,
56        &expected_authority_keys,
57        &clock,
58        is_vote_authorize_with_bls_enabled,
59        consume_pop_compute_units,
60    )
61}
62
63fn is_init_account_v2_enabled(invoke_context: &InvokeContext) -> bool {
64    let feature_set = invoke_context.get_feature_set();
65    feature_set.bls_pubkey_management_in_vote_account
66        && feature_set.commission_rate_in_basis_points
67        && feature_set.custom_commission_collector
68        && feature_set.block_revenue_sharing
69        && feature_set.vote_account_initialize_v2
70}
71
72fn is_vote_authorize_with_bls_enabled(invoke_context: &InvokeContext) -> bool {
73    invoke_context
74        .get_feature_set()
75        .bls_pubkey_management_in_vote_account
76}
77
78fn should_reject_legacy_vote_instructions(invoke_context: &InvokeContext) -> bool {
79    invoke_context.is_deprecate_legacy_vote_ixs_active()
80        || invoke_context.is_alpenglow_migration_succeeded()
81}
82
83// Citing `runtime/src/block_cost_limit.rs`, vote has statically defined 2100
84// units; can consume based on instructions in the future like `bpf_loader` does.
85pub const DEFAULT_COMPUTE_UNITS: u64 = 2_100;
86
87/// Cost in compute units for BLS proof-of-possession verification (SIMD-0387).
88pub const BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS: u64 = 34_500;
89
90declare_process_instruction!(Entrypoint, DEFAULT_COMPUTE_UNITS, |invoke_context| {
91    let transaction_context = &invoke_context.transaction_context;
92    let instruction_context = transaction_context.get_current_instruction_context()?;
93    let data = instruction_context.get_instruction_data();
94
95    trace!("process_instruction: {data:?}");
96
97    let mut me = instruction_context.try_borrow_instruction_account(0)?;
98    if *me.get_owner() != id() {
99        return Err(InstructionError::InvalidAccountOwner);
100    }
101
102    // Determine the target vote state version to use for all operations.
103    let target_version = VoteStateTargetVersion::V4;
104
105    let signers = instruction_context.get_signers()?;
106    let is_init_account_v2_enabled = is_init_account_v2_enabled(invoke_context);
107    let is_vote_authorize_with_bls_enabled = is_vote_authorize_with_bls_enabled(invoke_context);
108    let consume_pop_compute_units = || {
109        invoke_context
110            .consume_checked(BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS)
111            .map_err(|_| InstructionError::ComputationalBudgetExceeded)
112    };
113    let instruction: VoteInstruction =
114        wincode::config::deserialize(data, PACKET_DATA_WINCODE_CONFIG)
115            .map_err(|_| InstructionError::InvalidInstructionData)?;
116    match instruction {
117        VoteInstruction::InitializeAccount(vote_init) => {
118            let rent =
119                get_sysvar_with_account_check::rent(invoke_context, &instruction_context, 1)?;
120            if !rent.is_exempt(me.get_lamports(), me.get_data().len()) {
121                return Err(InstructionError::InsufficientFunds);
122            }
123            let clock =
124                get_sysvar_with_account_check::clock(invoke_context, &instruction_context, 2)?;
125            vote_state::initialize_account(&mut me, target_version, &vote_init, &signers, &clock)
126        }
127        VoteInstruction::Authorize(voter_pubkey, vote_authorize) => {
128            let clock =
129                get_sysvar_with_account_check::clock(invoke_context, &instruction_context, 1)?;
130            vote_state::authorize(
131                &mut me,
132                target_version,
133                &voter_pubkey,
134                vote_authorize,
135                &signers,
136                &clock,
137                is_vote_authorize_with_bls_enabled,
138                consume_pop_compute_units,
139            )
140        }
141        VoteInstruction::AuthorizeWithSeed(args) => {
142            instruction_context.check_number_of_instruction_accounts(3)?;
143            process_authorize_with_seed_instruction(
144                invoke_context,
145                &instruction_context,
146                &mut me,
147                target_version,
148                &args.new_authority,
149                args.authorization_type,
150                &args.current_authority_derived_key_owner,
151                args.current_authority_derived_key_seed.as_str(),
152                is_vote_authorize_with_bls_enabled,
153                consume_pop_compute_units,
154            )
155        }
156        VoteInstruction::AuthorizeCheckedWithSeed(args) => {
157            instruction_context.check_number_of_instruction_accounts(4)?;
158            let new_authority = instruction_context.get_key_of_instruction_account(3)?;
159            if !instruction_context.is_instruction_account_signer(3)? {
160                return Err(InstructionError::MissingRequiredSignature);
161            }
162            process_authorize_with_seed_instruction(
163                invoke_context,
164                &instruction_context,
165                &mut me,
166                target_version,
167                new_authority,
168                args.authorization_type,
169                &args.current_authority_derived_key_owner,
170                args.current_authority_derived_key_seed.as_str(),
171                is_vote_authorize_with_bls_enabled,
172                consume_pop_compute_units,
173            )
174        }
175        VoteInstruction::UpdateValidatorIdentity => {
176            instruction_context.check_number_of_instruction_accounts(2)?;
177            let node_pubkey = instruction_context.get_key_of_instruction_account(1)?;
178            let custom_collector_enabled =
179                invoke_context.get_feature_set().custom_commission_collector;
180            vote_state::update_validator_identity(
181                &mut me,
182                target_version,
183                node_pubkey,
184                &signers,
185                custom_collector_enabled,
186            )
187        }
188        VoteInstruction::UpdateCommission(commission) => {
189            let sysvar_cache = invoke_context.environment_config.sysvar_cache();
190
191            // Disable the commission update rule after the "delay commission
192            // update" feature is activated because it imposes a minimum delay
193            // of one full epoch before the new commission rate takes effect.
194            let disable_commission_update_rule =
195                invoke_context.get_feature_set().delay_commission_updates;
196
197            vote_state::update_commission(
198                &mut me,
199                target_version,
200                commission,
201                &signers,
202                sysvar_cache.get_epoch_schedule()?.as_ref(),
203                sysvar_cache.get_clock()?.as_ref(),
204                disable_commission_update_rule,
205            )
206        }
207        VoteInstruction::Vote(vote) | VoteInstruction::VoteSwitch(vote, _) => {
208            if should_reject_legacy_vote_instructions(invoke_context) {
209                return Err(InstructionError::InvalidInstructionData);
210            }
211            let slot_hashes = get_sysvar_with_account_check::slot_hashes(
212                invoke_context,
213                &instruction_context,
214                1,
215            )?;
216            let clock =
217                get_sysvar_with_account_check::clock(invoke_context, &instruction_context, 2)?;
218            vote_state::process_vote_with_account(
219                &mut me,
220                target_version,
221                &slot_hashes,
222                &clock,
223                &vote,
224                &signers,
225            )
226        }
227        VoteInstruction::UpdateVoteState(vote_state_update)
228        | VoteInstruction::UpdateVoteStateSwitch(vote_state_update, _) => {
229            if should_reject_legacy_vote_instructions(invoke_context) {
230                return Err(InstructionError::InvalidInstructionData);
231            }
232            let sysvar_cache = invoke_context.environment_config.sysvar_cache();
233            let slot_hashes = sysvar_cache.get_slot_hashes()?;
234            let clock = sysvar_cache.get_clock()?;
235            vote_state::process_vote_state_update(
236                &mut me,
237                target_version,
238                slot_hashes.slot_hashes(),
239                &clock,
240                vote_state_update,
241                &signers,
242            )
243        }
244        VoteInstruction::CompactUpdateVoteState(vote_state_update)
245        | VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, _) => {
246            if should_reject_legacy_vote_instructions(invoke_context) {
247                return Err(InstructionError::InvalidInstructionData);
248            }
249            let sysvar_cache = invoke_context.environment_config.sysvar_cache();
250            let slot_hashes = sysvar_cache.get_slot_hashes()?;
251            let clock = sysvar_cache.get_clock()?;
252            vote_state::process_vote_state_update(
253                &mut me,
254                target_version,
255                slot_hashes.slot_hashes(),
256                &clock,
257                vote_state_update,
258                &signers,
259            )
260        }
261        VoteInstruction::TowerSync(tower_sync)
262        | VoteInstruction::TowerSyncSwitch(tower_sync, _) => {
263            if invoke_context.is_alpenglow_migration_succeeded() {
264                return Err(InstructionError::InvalidInstructionData);
265            }
266            let sysvar_cache = invoke_context.environment_config.sysvar_cache();
267            let slot_hashes = sysvar_cache.get_slot_hashes()?;
268            let clock = sysvar_cache.get_clock()?;
269            vote_state::process_tower_sync(
270                &mut me,
271                target_version,
272                slot_hashes.slot_hashes(),
273                &clock,
274                tower_sync,
275                &signers,
276            )
277        }
278        VoteInstruction::Withdraw(lamports) => {
279            instruction_context.check_number_of_instruction_accounts(2)?;
280            let rent_sysvar = invoke_context
281                .environment_config
282                .sysvar_cache()
283                .get_rent()?;
284            let clock_sysvar = invoke_context
285                .environment_config
286                .sysvar_cache()
287                .get_clock()?;
288
289            drop(me);
290            vote_state::withdraw(
291                &instruction_context,
292                0,
293                target_version,
294                lamports,
295                1,
296                &signers,
297                &rent_sysvar,
298                &clock_sysvar,
299            )
300        }
301        VoteInstruction::AuthorizeChecked(vote_authorize) => {
302            instruction_context.check_number_of_instruction_accounts(4)?;
303            let voter_pubkey = instruction_context.get_key_of_instruction_account(3)?;
304            if !instruction_context.is_instruction_account_signer(3)? {
305                return Err(InstructionError::MissingRequiredSignature);
306            }
307            let clock =
308                get_sysvar_with_account_check::clock(invoke_context, &instruction_context, 1)?;
309            vote_state::authorize(
310                &mut me,
311                target_version,
312                voter_pubkey,
313                vote_authorize,
314                &signers,
315                &clock,
316                is_vote_authorize_with_bls_enabled,
317                consume_pop_compute_units,
318            )
319        }
320        VoteInstruction::InitializeAccountV2(vote_init_v2) => {
321            if !is_init_account_v2_enabled {
322                return Err(InstructionError::InvalidInstructionData);
323            }
324
325            instruction_context.check_number_of_instruction_accounts(4)?;
326
327            let sysvar_cache = invoke_context.environment_config.sysvar_cache();
328            let clock = sysvar_cache.get_clock()?;
329            let rent = sysvar_cache.get_rent()?;
330
331            drop(me);
332            vote_state::initialize_account_v2(
333                &instruction_context,
334                /* vote_account_index */ 0,
335                target_version,
336                &vote_init_v2,
337                /* inflation_rewards_collector_index */ 2,
338                /* block_revenue_collector_index */ 3,
339                &signers,
340                &clock,
341                &rent,
342                consume_pop_compute_units,
343            )
344        }
345        VoteInstruction::UpdateCommissionBps {
346            commission_bps,
347            kind,
348        } => {
349            // SIMD-0291: Commission Rate in Basis Points
350            // Requires SIMD-0185: Vote State V4
351            // Requires SIMD-0249: Delay Commission Updates
352            let feature_set = invoke_context.get_feature_set();
353            if !feature_set.commission_rate_in_basis_points || !feature_set.delay_commission_updates
354            {
355                return Err(InstructionError::InvalidInstructionData);
356            }
357            vote_state::update_commission_bps(
358                &mut me,
359                target_version,
360                commission_bps,
361                kind,
362                &signers,
363                feature_set.block_revenue_sharing,
364            )
365        }
366        VoteInstruction::UpdateCommissionCollector(kind) => {
367            // SIMD-0232: Custom Commission Collector Account
368            // Requires SIMD-0185: Vote State V4
369            let custom_collector_enabled =
370                invoke_context.get_feature_set().custom_commission_collector;
371            if !custom_collector_enabled {
372                return Err(InstructionError::InvalidInstructionData);
373            }
374
375            instruction_context.check_number_of_instruction_accounts(3)?;
376
377            let rent = invoke_context
378                .environment_config
379                .sysvar_cache()
380                .get_rent()?;
381
382            drop(me);
383            vote_state::update_commission_collector(
384                &instruction_context,
385                /* vote_account_index */ 0,
386                target_version,
387                /* new_collector_index */ 1,
388                kind,
389                &signers,
390                &rent,
391            )
392        }
393        VoteInstruction::DepositDelegatorRewards { .. } => {
394            return Err(InstructionError::InvalidInstructionData);
395        }
396    }
397});
398
399#[allow(clippy::arithmetic_side_effects)]
400#[cfg(test)]
401mod tests {
402    use {
403        super::*,
404        crate::{
405            vote_error::VoteError,
406            vote_instruction::{
407                CreateVoteAccountConfig, VoteInstruction, authorize, authorize_checked,
408                compact_update_vote_state, compact_update_vote_state_switch,
409                create_account_with_config, update_commission, update_validator_identity,
410                update_vote_state, update_vote_state_switch, vote, vote_switch, withdraw,
411            },
412            vote_state::{
413                self, Lockout, TowerSync, Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs,
414                VoteAuthorizeWithSeedArgs, VoteInit, VoteInitV2, VoteStateUpdate, VoteStateV3,
415                VoteStateV4, VoteStateVersions, create_bls_pubkey_and_proof_of_possession,
416                handler::VoteStateHandler,
417            },
418        },
419        solana_account::{
420            Account, AccountSharedData, ReadableAccount, WritableAccount,
421            state_traits::StateMutWincode as _,
422        },
423        solana_clock::Clock,
424        solana_epoch_schedule::EpochSchedule,
425        solana_hash::Hash,
426        solana_instruction::{AccountMeta, Instruction},
427        solana_program_runtime::{
428            invoke_context::mock_process_instruction_with_feature_set,
429            program_cache_entry::ProgramCacheEntry,
430            solana_sbpf::{program::BuiltinFunctionDefinition, vm::ContextObject},
431        },
432        solana_pubkey::Pubkey,
433        solana_rent::Rent,
434        solana_sdk_ids::sysvar,
435        solana_slot_hashes::{SlotHash, SlotHashes},
436        solana_svm_feature_set::SVMFeatureSet,
437        solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS as SYSTEM_PROGRAM_COMPUTE_UNITS,
438        solana_sysvar_id::SysvarId,
439        solana_vote_interface::{
440            instruction::{CommissionKind, tower_sync, tower_sync_switch},
441            state::{
442                BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, BLS_PUBLIC_KEY_COMPRESSED_SIZE,
443                VoterWithBLSArgs,
444            },
445        },
446        std::{cell::RefCell, collections::HashSet, str::FromStr, sync::Arc},
447        test_case::test_matrix,
448    };
449
450    fn create_sysvar_account<T>(value: &T) -> AccountSharedData
451    where
452        T: wincode::SchemaWrite<solana_account::WincodeConfig, Src = T>
453            + for<'de> wincode::SchemaRead<'de, solana_account::WincodeConfig, Dst = T>
454            + SysvarId,
455    {
456        let serialized_len = wincode::config::serialized_size(value, solana_account::WINCODE_CONFIG)
457            .unwrap() as usize;
458        let canonical_data_len = match T::id() {
459            sysvar::clock::ID => solana_clock::SIZE,
460            sysvar::epoch_schedule::ID => solana_epoch_schedule::SIZE,
461            sysvar::rent::ID => solana_rent::SIZE,
462            sysvar::slot_hashes::ID => solana_slot_hashes::SIZE,
463            id => panic!("unsupported sysvar: {id}"),
464        };
465        let required_data_len = canonical_data_len.max(serialized_len);
466        AccountSharedData::new_data_with_space(1, value, required_data_len, &sysvar::id()).unwrap()
467    }
468
469    fn vote_state_size_of() -> usize {
470        VoteStateV4::size_of()
471    }
472
473    fn deserialize_vote_state_for_test(
474        account_data: &[u8],
475        vote_pubkey: &Pubkey,
476    ) -> VoteStateHandler {
477        VoteStateHandler::new_v4(VoteStateV4::deserialize(account_data, vote_pubkey).unwrap())
478    }
479
480    struct VoteAccountTestFixtureWithAuthorities {
481        vote_account: AccountSharedData,
482        vote_pubkey: Pubkey,
483        voter_base_key: Pubkey,
484        voter_owner: Pubkey,
485        voter_seed: String,
486        withdrawer_base_key: Pubkey,
487        withdrawer_owner: Pubkey,
488        withdrawer_seed: String,
489    }
490
491    fn create_default_account() -> AccountSharedData {
492        AccountSharedData::new(0, 0, &Pubkey::new_unique())
493    }
494
495    #[derive(Clone, Copy, Default)]
496    struct VoteProgramFeatures {
497        bls_pubkey_management_in_vote_account: bool,
498        commission_rate_in_basis_points: bool,
499        custom_commission_collector: bool,
500        block_revenue_sharing: bool,
501        vote_account_initialize_v2: bool,
502        alpenglow_migration_succeeded: bool,
503    }
504
505    impl VoteProgramFeatures {
506        fn all_enabled() -> Self {
507            Self {
508                bls_pubkey_management_in_vote_account: true,
509                commission_rate_in_basis_points: true,
510                custom_commission_collector: true,
511                block_revenue_sharing: true,
512                vote_account_initialize_v2: true,
513                alpenglow_migration_succeeded: false,
514            }
515        }
516    }
517
518    fn process_instruction(
519        features: VoteProgramFeatures,
520        instruction_data: &[u8],
521        transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
522        instruction_accounts: Vec<AccountMeta>,
523        expected_result: Result<(), InstructionError>,
524    ) -> Vec<AccountSharedData> {
525        process_instruction_with_cu_check(
526            features,
527            instruction_data,
528            transaction_accounts,
529            instruction_accounts,
530            expected_result,
531            DEFAULT_COMPUTE_UNITS,
532        )
533    }
534
535    fn process_instruction_with_cu_check(
536        features: VoteProgramFeatures,
537        instruction_data: &[u8],
538        transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
539        instruction_accounts: Vec<AccountMeta>,
540        expected_result: Result<(), InstructionError>,
541        expected_cus: u64,
542    ) -> Vec<AccountSharedData> {
543        let VoteProgramFeatures {
544            bls_pubkey_management_in_vote_account,
545            commission_rate_in_basis_points,
546            custom_commission_collector,
547            block_revenue_sharing,
548            vote_account_initialize_v2,
549            alpenglow_migration_succeeded,
550        } = features;
551        let cu_consumed = RefCell::new(0u64);
552        let accounts = mock_process_instruction_with_feature_set(
553            &id(),
554            instruction_data,
555            transaction_accounts,
556            instruction_accounts,
557            expected_result,
558            Entrypoint::register,
559            |invoke_context| {
560                invoke_context
561                    .set_alpenglow_migration_succeeded_for_tests(alpenglow_migration_succeeded);
562                // Register system program for CPI support.
563                invoke_context.program_cache_for_tx_batch.replenish(
564                    solana_sdk_ids::system_program::id(),
565                    Arc::new(ProgramCacheEntry::new_builtin(
566                        solana_system_program::system_processor::Entrypoint::register,
567                    )),
568                );
569                *cu_consumed.borrow_mut() = invoke_context.get_remaining();
570            },
571            |invoke_context| {
572                *cu_consumed.borrow_mut() -= invoke_context.get_remaining();
573            },
574            &SVMFeatureSet {
575                bls_pubkey_management_in_vote_account,
576                commission_rate_in_basis_points,
577                custom_commission_collector,
578                block_revenue_sharing,
579                vote_account_initialize_v2,
580                ..SVMFeatureSet::all_enabled()
581            },
582        );
583        assert_eq!(
584            *cu_consumed.borrow(),
585            expected_cus,
586            "Expected {} CU consumed, got {}",
587            expected_cus,
588            *cu_consumed.borrow()
589        );
590        accounts
591    }
592
593    fn process_instruction_as_one_arg(
594        features: VoteProgramFeatures,
595        instruction: &Instruction,
596        expected_result: Result<(), InstructionError>,
597    ) -> Vec<AccountSharedData> {
598        process_instruction_as_one_arg_with_cu_check(
599            features,
600            instruction,
601            expected_result,
602            DEFAULT_COMPUTE_UNITS,
603        )
604    }
605
606    fn process_instruction_as_one_arg_with_cu_check(
607        features: VoteProgramFeatures,
608        instruction: &Instruction,
609        expected_result: Result<(), InstructionError>,
610        expected_cus: u64,
611    ) -> Vec<AccountSharedData> {
612        let mut pubkeys: HashSet<Pubkey> = instruction
613            .accounts
614            .iter()
615            .map(|meta| meta.pubkey)
616            .collect();
617        pubkeys.insert(sysvar::clock::id());
618        pubkeys.insert(sysvar::epoch_schedule::id());
619        pubkeys.insert(sysvar::rent::id());
620        pubkeys.insert(sysvar::slot_hashes::id());
621        let transaction_accounts: Vec<_> = pubkeys
622            .iter()
623            .map(|pubkey| {
624                (
625                    *pubkey,
626                    if sysvar::clock::check_id(pubkey) {
627                        create_sysvar_account(&Clock::default())
628                    } else if sysvar::epoch_schedule::check_id(pubkey) {
629                        create_sysvar_account(&EpochSchedule::without_warmup())
630                    } else if sysvar::slot_hashes::check_id(pubkey) {
631                        create_sysvar_account(&SlotHashes::default())
632                    } else if sysvar::rent::check_id(pubkey) {
633                        create_sysvar_account(&Rent::free())
634                    } else if *pubkey == invalid_vote_state_pubkey() {
635                        AccountSharedData::from(Account {
636                            owner: invalid_vote_state_pubkey(),
637                            ..Account::default()
638                        })
639                    } else {
640                        AccountSharedData::from(Account {
641                            owner: id(),
642                            ..Account::default()
643                        })
644                    },
645                )
646            })
647            .collect();
648        process_instruction_with_cu_check(
649            features,
650            &instruction.data,
651            transaction_accounts,
652            instruction.accounts.clone(),
653            expected_result,
654            expected_cus,
655        )
656    }
657
658    fn invalid_vote_state_pubkey() -> Pubkey {
659        Pubkey::from_str("BadVote111111111111111111111111111111111111").unwrap()
660    }
661
662    fn create_default_rent_account() -> AccountSharedData {
663        create_sysvar_account(&Rent::free())
664    }
665
666    fn create_default_clock_account() -> AccountSharedData {
667        create_sysvar_account(&Clock::default())
668    }
669
670    fn create_test_account() -> (Pubkey, AccountSharedData) {
671        let rent = Rent::default();
672        let vote_pubkey = solana_pubkey::new_rand();
673        let node_pubkey = solana_pubkey::new_rand();
674
675        let balance = rent.minimum_balance(VoteStateV4::size_of());
676        let account = vote_state::create_v4_account_with_authorized(
677            &node_pubkey,
678            &vote_pubkey,
679            [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
680            &vote_pubkey,
681            0,
682            &vote_pubkey,
683            0,
684            &node_pubkey,
685            balance,
686        );
687
688        (vote_pubkey, account)
689    }
690
691    /// Create a V4 vote account with `bls_pubkey_compressed: None`, mirroring
692    /// what an account looks like after `InitializeAccount` (the v1
693    /// instruction) but before any BLS pubkey has been registered.
694    fn create_test_account_no_bls_key() -> (Pubkey, AccountSharedData) {
695        let rent = Rent::default();
696        let vote_pubkey = solana_pubkey::new_rand();
697        let node_pubkey = solana_pubkey::new_rand();
698        let balance = rent.minimum_balance(VoteStateV4::size_of());
699
700        let mut account = AccountSharedData::new(balance, VoteStateV4::size_of(), &id());
701        let vote_state = VoteStateV4::new_with_defaults(
702            &vote_pubkey,
703            &VoteInit {
704                node_pubkey,
705                authorized_voter: vote_pubkey,
706                authorized_withdrawer: vote_pubkey,
707                commission: 0,
708            },
709            &Clock::default(),
710        );
711        VoteStateV4::serialize(
712            &VoteStateVersions::V4(Box::new(vote_state)),
713            account.data_as_mut_slice(),
714        )
715        .unwrap();
716
717        (vote_pubkey, account)
718    }
719
720    /// Create a vote account whose stored representation is `VoteStateV3`
721    /// (rather than V4). Useful for verifying instructions that require a V4
722    /// storage layout reject pre-V4 accounts.
723    fn create_test_account_v3() -> (Pubkey, AccountSharedData) {
724        let rent = Rent::default();
725        let vote_pubkey = solana_pubkey::new_rand();
726        let node_pubkey = solana_pubkey::new_rand();
727        let balance = rent.minimum_balance(VoteStateV3::size_of());
728
729        let mut account = AccountSharedData::new(balance, VoteStateV3::size_of(), &id());
730        let vote_state = VoteStateV3::new(
731            &VoteInit {
732                node_pubkey,
733                authorized_voter: vote_pubkey,
734                authorized_withdrawer: vote_pubkey,
735                commission: 0,
736            },
737            &Clock::default(),
738        );
739        VoteStateV3::serialize(
740            &VoteStateVersions::V3(Box::new(vote_state)),
741            account.data_as_mut_slice(),
742        )
743        .unwrap();
744
745        (vote_pubkey, account)
746    }
747
748    fn create_test_account_with_authorized() -> (Pubkey, Pubkey, Pubkey, AccountSharedData) {
749        let vote_pubkey = solana_pubkey::new_rand();
750        let authorized_voter = solana_pubkey::new_rand();
751        let authorized_withdrawer = solana_pubkey::new_rand();
752        let account =
753            create_test_account_with_provided_authorized(&authorized_voter, &authorized_withdrawer);
754
755        (
756            vote_pubkey,
757            authorized_voter,
758            authorized_withdrawer,
759            account,
760        )
761    }
762
763    fn create_test_account_with_provided_authorized(
764        authorized_voter: &Pubkey,
765        authorized_withdrawer: &Pubkey,
766    ) -> AccountSharedData {
767        let node_pubkey = solana_pubkey::new_rand();
768
769        vote_state::create_v4_account_with_authorized(
770            &node_pubkey,
771            authorized_voter,
772            [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
773            authorized_withdrawer,
774            0,
775            authorized_withdrawer,
776            0,
777            &node_pubkey,
778            100,
779        )
780    }
781
782    fn create_test_account_with_authorized_from_seed() -> VoteAccountTestFixtureWithAuthorities {
783        let vote_pubkey = Pubkey::new_unique();
784        let voter_base_key = Pubkey::new_unique();
785        let voter_owner = Pubkey::new_unique();
786        let voter_seed = String::from("VOTER_SEED");
787        let withdrawer_base_key = Pubkey::new_unique();
788        let withdrawer_owner = Pubkey::new_unique();
789        let withdrawer_seed = String::from("WITHDRAWER_SEED");
790        let authorized_voter =
791            Pubkey::create_with_seed(&voter_base_key, voter_seed.as_str(), &voter_owner).unwrap();
792        let authorized_withdrawer = Pubkey::create_with_seed(
793            &withdrawer_base_key,
794            withdrawer_seed.as_str(),
795            &withdrawer_owner,
796        )
797        .unwrap();
798
799        let node_pubkey = Pubkey::new_unique();
800        let vote_account = vote_state::create_v4_account_with_authorized(
801            &node_pubkey,
802            &authorized_voter,
803            [0u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
804            &authorized_withdrawer,
805            0,
806            &authorized_withdrawer,
807            0,
808            &node_pubkey,
809            100,
810        );
811
812        VoteAccountTestFixtureWithAuthorities {
813            vote_account,
814            vote_pubkey,
815            voter_base_key,
816            voter_owner,
817            voter_seed,
818            withdrawer_base_key,
819            withdrawer_owner,
820            withdrawer_seed,
821        }
822    }
823
824    fn create_test_account_with_epoch_credits(
825        credits_to_append: &[u64],
826    ) -> (Pubkey, AccountSharedData) {
827        let vote_pubkey = solana_pubkey::new_rand();
828        let node_pubkey = solana_pubkey::new_rand();
829
830        let vote_init = VoteInit {
831            node_pubkey,
832            authorized_voter: vote_pubkey,
833            authorized_withdrawer: vote_pubkey,
834            commission: 0,
835        };
836        let clock = Clock::default();
837
838        let space = vote_state_size_of();
839        let lamports = Rent::default().minimum_balance(space);
840
841        let v4 = VoteStateV4::new_with_defaults(&vote_pubkey, &vote_init, &clock);
842        let mut vote_state = VoteStateHandler::new_v4(v4);
843
844        let epoch_credits = vote_state.epoch_credits_mut();
845        epoch_credits.clear();
846
847        let mut current_epoch_credits: u64 = 0;
848        let mut previous_epoch_credits = 0;
849        for (epoch, credits) in credits_to_append.iter().enumerate() {
850            current_epoch_credits = current_epoch_credits.saturating_add(*credits);
851            epoch_credits.push((
852                u64::try_from(epoch).unwrap(),
853                current_epoch_credits,
854                previous_epoch_credits,
855            ));
856            previous_epoch_credits = current_epoch_credits;
857        }
858
859        let account = AccountSharedData::new_with_data(lamports, vote_state.serialize(), &id());
860
861        (vote_pubkey, account)
862    }
863
864    /// Returns Vec of serialized VoteInstruction and flag indicating if it is a tower sync
865    /// variant, along with the original vote
866    fn create_serialized_votes() -> (Vote, Vec<(Vec<u8>, bool)>) {
867        let vote = Vote::new(vec![1], Hash::default());
868        let vote_state_update = VoteStateUpdate::from(vec![(1, 1)]);
869        let tower_sync = TowerSync::from(vec![(1, 1)]);
870        (
871            vote.clone(),
872            vec![
873                (
874                    wincode::serialize(&VoteInstruction::Vote(vote)).unwrap(),
875                    false,
876                ),
877                (
878                    wincode::serialize(&VoteInstruction::UpdateVoteState(
879                        vote_state_update.clone(),
880                    ))
881                    .unwrap(),
882                    false,
883                ),
884                (
885                    wincode::serialize(&VoteInstruction::CompactUpdateVoteState(vote_state_update))
886                        .unwrap(),
887                    false,
888                ),
889                (
890                    wincode::serialize(&VoteInstruction::TowerSync(tower_sync)).unwrap(),
891                    true,
892                ),
893            ],
894        )
895    }
896
897    #[test]
898    fn test_vote_process_instruction_decode_bail() {
899        process_instruction(
900            VoteProgramFeatures {
901                ..Default::default()
902            },
903            &[],
904            Vec::new(),
905            Vec::new(),
906            Err(InstructionError::MissingAccount),
907        );
908    }
909
910    #[test_matrix(
911        [false, true],
912        [false, true],
913        [false, true],
914        [false, true],
915        [false, true]
916    )]
917    fn test_initialize_vote_account(
918        bls_pubkey_management_in_vote_account: bool,
919        commission_rate_in_basis_points: bool,
920        custom_commission_collector: bool,
921        block_revenue_sharing: bool,
922        vote_account_initialize_v2: bool,
923    ) {
924        let vote_pubkey = solana_pubkey::new_rand();
925        let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
926        let node_pubkey = solana_pubkey::new_rand();
927        let node_account = AccountSharedData::default();
928        let instruction_data = wincode::serialize(&VoteInstruction::InitializeAccount(VoteInit {
929            node_pubkey,
930            authorized_voter: vote_pubkey,
931            authorized_withdrawer: vote_pubkey,
932            commission: 0,
933        }))
934        .unwrap();
935        let mut instruction_accounts = vec![
936            AccountMeta {
937                pubkey: vote_pubkey,
938                is_signer: false,
939                is_writable: true,
940            },
941            AccountMeta {
942                pubkey: sysvar::rent::id(),
943                is_signer: false,
944                is_writable: false,
945            },
946            AccountMeta {
947                pubkey: sysvar::clock::id(),
948                is_signer: false,
949                is_writable: false,
950            },
951            AccountMeta {
952                pubkey: node_pubkey,
953                is_signer: true,
954                is_writable: false,
955            },
956        ];
957
958        let features = VoteProgramFeatures {
959            bls_pubkey_management_in_vote_account,
960            commission_rate_in_basis_points,
961            custom_commission_collector,
962            block_revenue_sharing,
963            vote_account_initialize_v2,
964            alpenglow_migration_succeeded: false,
965        };
966
967        let accounts = process_instruction(
968            features,
969            &instruction_data,
970            vec![
971                (vote_pubkey, vote_account.clone()),
972                (sysvar::rent::id(), create_default_rent_account()),
973                (sysvar::clock::id(), create_default_clock_account()),
974                (node_pubkey, node_account.clone()),
975            ],
976            instruction_accounts.clone(),
977            Ok(()),
978        );
979
980        // reinit should fail
981        process_instruction(
982            features,
983            &instruction_data,
984            vec![
985                (vote_pubkey, accounts[0].clone()),
986                (sysvar::rent::id(), create_default_rent_account()),
987                (sysvar::clock::id(), create_default_clock_account()),
988                (node_pubkey, accounts[3].clone()),
989            ],
990            instruction_accounts.clone(),
991            Err(InstructionError::AccountAlreadyInitialized),
992        );
993
994        // init should fail, account is too big
995        process_instruction(
996            features,
997            &instruction_data,
998            vec![
999                (
1000                    vote_pubkey,
1001                    AccountSharedData::new(100, 2 * vote_state_size_of(), &id()),
1002                ),
1003                (sysvar::rent::id(), create_default_rent_account()),
1004                (sysvar::clock::id(), create_default_clock_account()),
1005                (node_pubkey, node_account.clone()),
1006            ],
1007            instruction_accounts.clone(),
1008            Err(InstructionError::InvalidAccountData),
1009        );
1010
1011        // init should fail, node_pubkey didn't sign the transaction
1012        instruction_accounts[3].is_signer = false;
1013        process_instruction(
1014            features,
1015            &instruction_data,
1016            vec![
1017                (vote_pubkey, vote_account),
1018                (sysvar::rent::id(), create_default_rent_account()),
1019                (sysvar::clock::id(), create_default_clock_account()),
1020                (node_pubkey, node_account),
1021            ],
1022            instruction_accounts.clone(),
1023            Err(InstructionError::MissingRequiredSignature),
1024        );
1025    }
1026
1027    #[test_matrix(
1028        [false, true],
1029        [false, true],
1030        [false, true],
1031        [false, true],
1032        [false, true]
1033    )]
1034    fn test_initialize_vote_account_v2(
1035        bls_pubkey_management_in_vote_account: bool,
1036        commission_rate_in_basis_points: bool,
1037        custom_commission_collector: bool,
1038        block_revenue_sharing: bool,
1039        vote_account_initialize_v2: bool,
1040    ) {
1041        let vote_pubkey = solana_pubkey::new_rand();
1042        let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
1043        let node_pubkey = solana_pubkey::new_rand();
1044        let node_account = AccountSharedData::default();
1045        let authorized_voter = solana_pubkey::new_rand();
1046        let authorized_withdrawer = solana_pubkey::new_rand();
1047        let (bls_pubkey, bls_proof_of_possession) =
1048            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
1049        let inflation_rewards_collector = solana_pubkey::new_rand();
1050        let inflation_rewards_collector_account =
1051            AccountSharedData::new(0, 0, &solana_sdk_ids::system_program::id());
1052        let block_revenue_collector = solana_pubkey::new_rand();
1053        let block_revenue_collector_account =
1054            AccountSharedData::new(0, 0, &solana_sdk_ids::system_program::id());
1055        let inflation_rewards_commission_bps = 1_234;
1056        let block_revenue_commission_bps = 5_678;
1057        let instruction_data =
1058            wincode::serialize(&VoteInstruction::InitializeAccountV2(VoteInitV2 {
1059                node_pubkey,
1060                authorized_voter,
1061                authorized_voter_bls_pubkey: bls_pubkey,
1062                authorized_voter_bls_proof_of_possession: bls_proof_of_possession,
1063                authorized_withdrawer,
1064                inflation_rewards_commission_bps,
1065                block_revenue_commission_bps,
1066            }))
1067            .unwrap();
1068        let mut instruction_accounts = vec![
1069            AccountMeta {
1070                pubkey: vote_pubkey,
1071                is_signer: false,
1072                is_writable: true,
1073            },
1074            AccountMeta {
1075                pubkey: node_pubkey,
1076                is_signer: true,
1077                is_writable: false,
1078            },
1079            AccountMeta {
1080                pubkey: inflation_rewards_collector,
1081                is_signer: false,
1082                is_writable: true,
1083            },
1084            AccountMeta {
1085                pubkey: block_revenue_collector,
1086                is_signer: false,
1087                is_writable: true,
1088            },
1089        ];
1090
1091        let features = VoteProgramFeatures {
1092            bls_pubkey_management_in_vote_account,
1093            commission_rate_in_basis_points,
1094            custom_commission_collector,
1095            block_revenue_sharing,
1096            vote_account_initialize_v2,
1097            alpenglow_migration_succeeded: false,
1098        };
1099
1100        let all_v2_features_enabled = bls_pubkey_management_in_vote_account
1101            && commission_rate_in_basis_points
1102            && custom_commission_collector
1103            && block_revenue_sharing
1104            && vote_account_initialize_v2;
1105
1106        // If any v2 feature is disabled, the new instruction should be rejected
1107        if !all_v2_features_enabled {
1108            process_instruction(
1109                features,
1110                &instruction_data,
1111                vec![
1112                    (vote_pubkey, vote_account),
1113                    (node_pubkey, node_account),
1114                    (
1115                        inflation_rewards_collector,
1116                        inflation_rewards_collector_account,
1117                    ),
1118                    (block_revenue_collector, block_revenue_collector_account),
1119                    (sysvar::rent::id(), create_default_rent_account()),
1120                    (sysvar::clock::id(), create_default_clock_account()),
1121                ],
1122                instruction_accounts.clone(),
1123                Err(InstructionError::InvalidInstructionData),
1124            );
1125            return;
1126        }
1127
1128        // Verify every field in the V4 state matches VoteInitV2 and the
1129        // collector accounts.
1130        let assert_v4_fields =
1131            |vote_account: &AccountSharedData,
1132             expected_inflation_rewards_collector: Pubkey,
1133             expected_block_revenue_collector: Pubkey| {
1134                let v4 = deserialize_vote_state_for_test(vote_account.data(), &vote_pubkey);
1135                let v4 = v4.as_ref_v4();
1136                assert_eq!(v4.node_pubkey, node_pubkey);
1137                assert_eq!(v4.authorized_withdrawer, authorized_withdrawer);
1138                assert_eq!(v4.bls_pubkey_compressed, Some(bls_pubkey));
1139                assert_eq!(
1140                    v4.inflation_rewards_commission_bps,
1141                    inflation_rewards_commission_bps
1142                );
1143                assert_eq!(
1144                    v4.inflation_rewards_collector,
1145                    expected_inflation_rewards_collector
1146                );
1147                assert_eq!(
1148                    v4.block_revenue_commission_bps,
1149                    block_revenue_commission_bps
1150                );
1151                assert_eq!(v4.block_revenue_collector, expected_block_revenue_collector);
1152                assert_eq!(v4.pending_delegator_rewards, 0);
1153                assert!(v4.votes.is_empty());
1154                assert!(v4.epoch_credits.is_empty());
1155                assert_eq!(v4.root_slot, None);
1156            };
1157
1158        let accounts = process_instruction_with_cu_check(
1159            features,
1160            &instruction_data,
1161            vec![
1162                (vote_pubkey, vote_account.clone()),
1163                (node_pubkey, node_account.clone()),
1164                (
1165                    inflation_rewards_collector,
1166                    inflation_rewards_collector_account.clone(),
1167                ),
1168                (
1169                    block_revenue_collector,
1170                    block_revenue_collector_account.clone(),
1171                ),
1172                (sysvar::rent::id(), create_default_rent_account()),
1173                (sysvar::clock::id(), create_default_clock_account()),
1174            ],
1175            instruction_accounts.clone(),
1176            Ok(()),
1177            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
1178        );
1179        assert_v4_fields(
1180            &accounts[0],
1181            inflation_rewards_collector,
1182            block_revenue_collector,
1183        );
1184
1185        // reinit should fail
1186        process_instruction(
1187            features,
1188            &instruction_data,
1189            vec![
1190                (vote_pubkey, accounts[0].clone()),
1191                (node_pubkey, accounts[1].clone()),
1192                (
1193                    inflation_rewards_collector,
1194                    inflation_rewards_collector_account.clone(),
1195                ),
1196                (
1197                    block_revenue_collector,
1198                    block_revenue_collector_account.clone(),
1199                ),
1200                (sysvar::rent::id(), create_default_rent_account()),
1201                (sysvar::clock::id(), create_default_clock_account()),
1202            ],
1203            instruction_accounts.clone(),
1204            Err(InstructionError::AccountAlreadyInitialized),
1205        );
1206
1207        // init should fail, account is too big
1208        process_instruction(
1209            features,
1210            &instruction_data,
1211            vec![
1212                (
1213                    vote_pubkey,
1214                    AccountSharedData::new(100, 2 * vote_state_size_of(), &id()),
1215                ),
1216                (node_pubkey, node_account.clone()),
1217                (
1218                    inflation_rewards_collector,
1219                    inflation_rewards_collector_account.clone(),
1220                ),
1221                (
1222                    block_revenue_collector,
1223                    block_revenue_collector_account.clone(),
1224                ),
1225                (sysvar::rent::id(), create_default_rent_account()),
1226                (sysvar::clock::id(), create_default_clock_account()),
1227            ],
1228            instruction_accounts.clone(),
1229            Err(InstructionError::InvalidAccountData),
1230        );
1231
1232        // init should fail, node_pubkey didn't sign the transaction
1233        instruction_accounts[1].is_signer = false;
1234        process_instruction(
1235            features,
1236            &instruction_data,
1237            vec![
1238                (vote_pubkey, vote_account.clone()),
1239                (node_pubkey, node_account.clone()),
1240                (
1241                    inflation_rewards_collector,
1242                    inflation_rewards_collector_account.clone(),
1243                ),
1244                (
1245                    block_revenue_collector,
1246                    block_revenue_collector_account.clone(),
1247                ),
1248                (sysvar::rent::id(), create_default_rent_account()),
1249                (sysvar::clock::id(), create_default_clock_account()),
1250            ],
1251            instruction_accounts.clone(),
1252            Err(InstructionError::MissingRequiredSignature),
1253        );
1254        instruction_accounts[1].is_signer = true;
1255
1256        // init should fail, fewer than 4 instruction accounts
1257        process_instruction(
1258            features,
1259            &instruction_data,
1260            vec![
1261                (vote_pubkey, vote_account.clone()),
1262                (node_pubkey, node_account.clone()),
1263                (
1264                    inflation_rewards_collector,
1265                    inflation_rewards_collector_account.clone(),
1266                ),
1267                (sysvar::rent::id(), create_default_rent_account()),
1268                (sysvar::clock::id(), create_default_clock_account()),
1269            ],
1270            instruction_accounts[..3].to_vec(),
1271            Err(InstructionError::MissingAccount),
1272        );
1273
1274        // init should pass with both collectors aliased to the vote account.
1275        let mut aliased_instruction_accounts = instruction_accounts.clone();
1276        aliased_instruction_accounts[2].pubkey = vote_pubkey;
1277        aliased_instruction_accounts[3].pubkey = vote_pubkey;
1278        let accounts = process_instruction_with_cu_check(
1279            features,
1280            &instruction_data,
1281            vec![
1282                (vote_pubkey, vote_account.clone()),
1283                (node_pubkey, node_account.clone()),
1284                (sysvar::rent::id(), create_default_rent_account()),
1285                (sysvar::clock::id(), create_default_clock_account()),
1286            ],
1287            aliased_instruction_accounts,
1288            Ok(()),
1289            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
1290        );
1291        assert_v4_fields(&accounts[0], vote_pubkey, vote_pubkey);
1292
1293        // init should pass with both collectors aliased to the same external
1294        // account.
1295        let mut aliased_instruction_accounts = instruction_accounts.clone();
1296        aliased_instruction_accounts[2].pubkey = inflation_rewards_collector;
1297        aliased_instruction_accounts[3].pubkey = inflation_rewards_collector;
1298        let accounts = process_instruction_with_cu_check(
1299            features,
1300            &instruction_data,
1301            vec![
1302                (vote_pubkey, vote_account),
1303                (node_pubkey, node_account),
1304                (
1305                    inflation_rewards_collector,
1306                    inflation_rewards_collector_account,
1307                ),
1308                (sysvar::rent::id(), create_default_rent_account()),
1309                (sysvar::clock::id(), create_default_clock_account()),
1310            ],
1311            aliased_instruction_accounts,
1312            Ok(()),
1313            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
1314        );
1315        assert_v4_fields(
1316            &accounts[0],
1317            inflation_rewards_collector,
1318            inflation_rewards_collector,
1319        );
1320    }
1321
1322    #[test]
1323    fn test_initialize_vote_account_v2_bad_proof_of_possession() {
1324        let vote_pubkey = solana_pubkey::new_rand();
1325        let vote_account = AccountSharedData::new(100, VoteStateV4::size_of(), &id());
1326        let node_pubkey = solana_pubkey::new_rand();
1327        let node_account = AccountSharedData::default();
1328        let inflation_rewards_collector = solana_pubkey::new_rand();
1329        let inflation_rewards_collector_account =
1330            AccountSharedData::new(0, 0, &solana_sdk_ids::system_program::id());
1331        let block_revenue_collector = solana_pubkey::new_rand();
1332        let block_revenue_collector_account =
1333            AccountSharedData::new(0, 0, &solana_sdk_ids::system_program::id());
1334        let instruction_with_bad_pop =
1335            wincode::serialize(&VoteInstruction::InitializeAccountV2(VoteInitV2 {
1336                node_pubkey,
1337                authorized_voter: vote_pubkey,
1338                authorized_withdrawer: vote_pubkey,
1339                authorized_voter_bls_pubkey: [1u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
1340                authorized_voter_bls_proof_of_possession: [2u8;
1341                    BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
1342                ..Default::default()
1343            }))
1344            .unwrap();
1345        let instruction_accounts = vec![
1346            AccountMeta {
1347                pubkey: vote_pubkey,
1348                is_signer: false,
1349                is_writable: true,
1350            },
1351            AccountMeta {
1352                pubkey: node_pubkey,
1353                is_signer: true,
1354                is_writable: false,
1355            },
1356            AccountMeta {
1357                pubkey: inflation_rewards_collector,
1358                is_signer: false,
1359                is_writable: true,
1360            },
1361            AccountMeta {
1362                pubkey: block_revenue_collector,
1363                is_signer: false,
1364                is_writable: true,
1365            },
1366        ];
1367        process_instruction_with_cu_check(
1368            VoteProgramFeatures::all_enabled(),
1369            &instruction_with_bad_pop,
1370            vec![
1371                (vote_pubkey, vote_account),
1372                (node_pubkey, node_account),
1373                (
1374                    inflation_rewards_collector,
1375                    inflation_rewards_collector_account.clone(),
1376                ),
1377                (
1378                    block_revenue_collector,
1379                    block_revenue_collector_account.clone(),
1380                ),
1381                (sysvar::rent::id(), create_default_rent_account()),
1382                (sysvar::clock::id(), create_default_clock_account()),
1383            ],
1384            instruction_accounts,
1385            Err(InstructionError::InvalidArgument),
1386            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
1387        );
1388    }
1389
1390    #[test_matrix([false, true])]
1391    fn test_vote_update_validator_identity(custom_commission_collector: bool) {
1392        let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) =
1393            create_test_account_with_authorized();
1394
1395        let original_block_revenue_collector = {
1396            let vote_state = deserialize_vote_state_for_test(vote_account.data(), &vote_pubkey);
1397            vote_state.as_ref_v4().block_revenue_collector
1398        };
1399
1400        let node_pubkey = solana_pubkey::new_rand();
1401        let instruction_data =
1402            wincode::serialize(&VoteInstruction::UpdateValidatorIdentity).unwrap();
1403        let transaction_accounts = vec![
1404            (vote_pubkey, vote_account),
1405            (node_pubkey, AccountSharedData::default()),
1406            (authorized_withdrawer, AccountSharedData::default()),
1407        ];
1408        let mut instruction_accounts = vec![
1409            AccountMeta {
1410                pubkey: vote_pubkey,
1411                is_signer: false,
1412                is_writable: true,
1413            },
1414            AccountMeta {
1415                pubkey: node_pubkey,
1416                is_signer: true,
1417                is_writable: false,
1418            },
1419            AccountMeta {
1420                pubkey: authorized_withdrawer,
1421                is_signer: true,
1422                is_writable: false,
1423            },
1424        ];
1425
1426        let features = VoteProgramFeatures {
1427            custom_commission_collector,
1428            ..Default::default()
1429        };
1430
1431        // should fail, node_pubkey didn't sign the transaction
1432        instruction_accounts[1].is_signer = false;
1433        let accounts = process_instruction(
1434            features,
1435            &instruction_data,
1436            transaction_accounts.clone(),
1437            instruction_accounts.clone(),
1438            Err(InstructionError::MissingRequiredSignature),
1439        );
1440        instruction_accounts[1].is_signer = true;
1441        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
1442        assert_ne!(*vote_state.node_pubkey(), node_pubkey);
1443
1444        // should fail, authorized_withdrawer didn't sign the transaction
1445        instruction_accounts[2].is_signer = false;
1446        let accounts = process_instruction(
1447            features,
1448            &instruction_data,
1449            transaction_accounts.clone(),
1450            instruction_accounts.clone(),
1451            Err(InstructionError::MissingRequiredSignature),
1452        );
1453        instruction_accounts[2].is_signer = true;
1454        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
1455        assert_ne!(*vote_state.node_pubkey(), node_pubkey);
1456
1457        // should pass
1458        let accounts = process_instruction(
1459            features,
1460            &instruction_data,
1461            transaction_accounts,
1462            instruction_accounts,
1463            Ok(()),
1464        );
1465        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
1466        assert_eq!(*vote_state.node_pubkey(), node_pubkey);
1467        if custom_commission_collector {
1468            // If SIMD-0232 is enabled, block revenue collector should be
1469            // unchanged.
1470            assert_eq!(
1471                vote_state.as_ref_v4().block_revenue_collector,
1472                original_block_revenue_collector,
1473            );
1474        } else {
1475            // If SIMD-0232 is disabled, block revenue collector should be
1476            // synced with identity.
1477            assert_eq!(vote_state.as_ref_v4().block_revenue_collector, node_pubkey);
1478        }
1479    }
1480
1481    #[test]
1482    fn test_vote_update_commission() {
1483        let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) =
1484            create_test_account_with_authorized();
1485        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommission(42)).unwrap();
1486        let transaction_accounts = vec![
1487            (vote_pubkey, vote_account),
1488            (authorized_withdrawer, AccountSharedData::default()),
1489            // Add the sysvar accounts so they're in the cache for mock processing
1490            (
1491                sysvar::clock::id(),
1492                create_sysvar_account(&Clock::default()),
1493            ),
1494            (
1495                sysvar::epoch_schedule::id(),
1496                create_sysvar_account(&EpochSchedule::without_warmup()),
1497            ),
1498        ];
1499        let mut instruction_accounts = vec![
1500            AccountMeta {
1501                pubkey: vote_pubkey,
1502                is_signer: false,
1503                is_writable: true,
1504            },
1505            AccountMeta {
1506                pubkey: authorized_withdrawer,
1507                is_signer: true,
1508                is_writable: false,
1509            },
1510        ];
1511
1512        let features = VoteProgramFeatures {
1513            ..Default::default()
1514        };
1515
1516        // should pass
1517        let accounts = process_instruction(
1518            features,
1519            &wincode::serialize(&VoteInstruction::UpdateCommission(200)).unwrap(),
1520            transaction_accounts.clone(),
1521            instruction_accounts.clone(),
1522            Ok(()),
1523        );
1524        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
1525        assert_eq!(vote_state.commission(), 200);
1526
1527        // should pass
1528        let accounts = process_instruction(
1529            features,
1530            &instruction_data,
1531            transaction_accounts.clone(),
1532            instruction_accounts.clone(),
1533            Ok(()),
1534        );
1535        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
1536        assert_eq!(vote_state.commission(), 42);
1537
1538        // should fail, authorized_withdrawer didn't sign the transaction
1539        instruction_accounts[1].is_signer = false;
1540        let accounts = process_instruction(
1541            features,
1542            &instruction_data,
1543            transaction_accounts,
1544            instruction_accounts,
1545            Err(InstructionError::MissingRequiredSignature),
1546        );
1547        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
1548        assert_eq!(vote_state.commission(), 0);
1549    }
1550
1551    #[test]
1552    fn test_vote_update_commission_bps() {
1553        // Test UpdateCommissionBps instruction (SIMD-0291).
1554        let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) =
1555            create_test_account_with_authorized();
1556
1557        let transaction_accounts = vec![
1558            (vote_pubkey, vote_account.clone()),
1559            (authorized_withdrawer, AccountSharedData::default()),
1560        ];
1561
1562        let instruction_accounts = vec![
1563            AccountMeta {
1564                pubkey: vote_pubkey,
1565                is_signer: false,
1566                is_writable: true,
1567            },
1568            AccountMeta {
1569                pubkey: authorized_withdrawer,
1570                is_signer: true,
1571                is_writable: false,
1572            },
1573        ];
1574
1575        let features = VoteProgramFeatures::all_enabled();
1576
1577        let get_commission_bps = |vote_account: &AccountSharedData, kind: &CommissionKind| {
1578            let vote_state = deserialize_vote_state_for_test(vote_account.data(), &vote_pubkey);
1579            match kind {
1580                CommissionKind::InflationRewards => {
1581                    vote_state.as_ref_v4().inflation_rewards_commission_bps
1582                }
1583                CommissionKind::BlockRevenue => vote_state.as_ref_v4().block_revenue_commission_bps,
1584            }
1585        };
1586
1587        let original_commission_bps =
1588            get_commission_bps(&vote_account, &CommissionKind::InflationRewards);
1589
1590        let commission_bps = 200; // 2%
1591
1592        for kind in [
1593            CommissionKind::InflationRewards,
1594            CommissionKind::BlockRevenue,
1595        ] {
1596            let other_kind = match kind {
1597                CommissionKind::InflationRewards => CommissionKind::BlockRevenue,
1598                CommissionKind::BlockRevenue => CommissionKind::InflationRewards,
1599            };
1600
1601            // Get the original commission for the other kind.
1602            let original_other_commission_bps = get_commission_bps(&vote_account, &other_kind);
1603
1604            let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionBps {
1605                commission_bps,
1606                kind: kind.clone(),
1607            })
1608            .unwrap();
1609
1610            // Should pass.
1611            let accounts = process_instruction(
1612                features,
1613                &instruction_data,
1614                transaction_accounts.clone(),
1615                instruction_accounts.clone(),
1616                Ok(()),
1617            );
1618            assert_eq!(get_commission_bps(&accounts[0], &kind), commission_bps);
1619
1620            // Verify the other commission kind was not affected.
1621            assert_eq!(
1622                get_commission_bps(&accounts[0], &other_kind),
1623                original_other_commission_bps,
1624            );
1625
1626            // Same value - should pass.
1627            let accounts = process_instruction(
1628                features,
1629                &instruction_data,
1630                vec![
1631                    (vote_pubkey, accounts[0].clone()),
1632                    (authorized_withdrawer, accounts[1].clone()),
1633                ],
1634                instruction_accounts.clone(),
1635                Ok(()),
1636            );
1637            assert_eq!(get_commission_bps(&accounts[0], &kind), commission_bps);
1638
1639            // Verify the other commission kind is still unchanged.
1640            assert_eq!(
1641                get_commission_bps(&accounts[0], &other_kind),
1642                original_other_commission_bps,
1643            );
1644        }
1645
1646        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionBps {
1647            commission_bps,
1648            kind: CommissionKind::InflationRewards,
1649        })
1650        .unwrap();
1651
1652        // Should fail - `CommissionKind::BlockRevenue` disallowed (SIMD-0123 disabled).
1653        let accounts = process_instruction(
1654            VoteProgramFeatures {
1655                block_revenue_sharing: false,
1656                ..features
1657            },
1658            &wincode::serialize(&VoteInstruction::UpdateCommissionBps {
1659                commission_bps,
1660                kind: CommissionKind::BlockRevenue,
1661            })
1662            .unwrap(),
1663            transaction_accounts.clone(),
1664            instruction_accounts.clone(),
1665            Err(InstructionError::InvalidInstructionData),
1666        );
1667        let stored_commission_bps = get_commission_bps(&accounts[0], &CommissionKind::BlockRevenue);
1668        assert_eq!(stored_commission_bps, 0); // BlockRevenue starts at 0
1669        assert_ne!(stored_commission_bps, commission_bps); // New value not set
1670
1671        // Should fail - authorized withdrawer didn't sign the transaction.
1672        let mut unsigned_instruction_accounts = instruction_accounts;
1673        unsigned_instruction_accounts[1].is_signer = false;
1674        let accounts = process_instruction(
1675            features,
1676            &instruction_data,
1677            transaction_accounts.clone(),
1678            unsigned_instruction_accounts,
1679            Err(InstructionError::MissingRequiredSignature),
1680        );
1681        let stored_commission_bps =
1682            get_commission_bps(&accounts[0], &CommissionKind::InflationRewards);
1683        assert_eq!(stored_commission_bps, original_commission_bps); // Matches original
1684        assert_ne!(stored_commission_bps, commission_bps); // New value not set
1685
1686        // Should fail - wrong signature for authorized withdrawer.
1687        let wrong_signer = Pubkey::new_unique();
1688        let mut wrong_signer_transaction_accounts = transaction_accounts;
1689        wrong_signer_transaction_accounts.push((wrong_signer, AccountSharedData::default()));
1690        let wrong_signer_instruction_accounts = vec![
1691            AccountMeta {
1692                pubkey: vote_pubkey,
1693                is_signer: false,
1694                is_writable: true,
1695            },
1696            AccountMeta {
1697                pubkey: wrong_signer,
1698                is_signer: true,
1699                is_writable: false,
1700            },
1701        ];
1702        let accounts = process_instruction(
1703            features,
1704            &instruction_data,
1705            wrong_signer_transaction_accounts,
1706            wrong_signer_instruction_accounts,
1707            Err(InstructionError::MissingRequiredSignature),
1708        );
1709        let stored_commission_bps =
1710            get_commission_bps(&accounts[0], &CommissionKind::InflationRewards);
1711        assert_eq!(stored_commission_bps, original_commission_bps); // Matches original
1712        assert_ne!(stored_commission_bps, commission_bps); // New value not set
1713    }
1714
1715    #[test]
1716    fn test_vote_update_commission_collector() {
1717        // Test UpdateCommissionCollector instruction (SIMD-0232).
1718        let custom_commission_collector = true;
1719
1720        let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) =
1721            create_test_account_with_authorized();
1722
1723        // Create a valid collector account: system-owned and rent-exempt.
1724        let new_collector_pubkey = Pubkey::new_unique();
1725        let rent = Rent::default();
1726        let rent_sysvar_account = create_sysvar_account(&rent);
1727        let collector_lamports = rent.minimum_balance(0);
1728        let new_collector_account =
1729            AccountSharedData::new(collector_lamports, 0, &solana_sdk_ids::system_program::id());
1730
1731        let transaction_accounts = vec![
1732            (vote_pubkey, vote_account.clone()),
1733            (new_collector_pubkey, new_collector_account),
1734            (authorized_withdrawer, AccountSharedData::default()),
1735            (sysvar::rent::id(), rent_sysvar_account),
1736        ];
1737
1738        let instruction_accounts = vec![
1739            AccountMeta {
1740                pubkey: vote_pubkey,
1741                is_signer: false,
1742                is_writable: true,
1743            },
1744            AccountMeta {
1745                pubkey: new_collector_pubkey,
1746                is_signer: false,
1747                is_writable: true,
1748            },
1749            AccountMeta {
1750                pubkey: authorized_withdrawer,
1751                is_signer: true,
1752                is_writable: false,
1753            },
1754        ];
1755
1756        let features = VoteProgramFeatures {
1757            custom_commission_collector,
1758            ..Default::default()
1759        };
1760
1761        let get_commission_collector = |vote_account: &AccountSharedData, kind: CommissionKind| {
1762            let vote_state = deserialize_vote_state_for_test(vote_account.data(), &vote_pubkey)
1763                .as_ref_v4()
1764                .clone();
1765            match kind {
1766                CommissionKind::InflationRewards => vote_state.inflation_rewards_collector,
1767                CommissionKind::BlockRevenue => vote_state.block_revenue_collector,
1768            }
1769        };
1770
1771        let original_inflation_collector =
1772            get_commission_collector(&vote_account, CommissionKind::InflationRewards);
1773        let original_block_revenue_collector =
1774            get_commission_collector(&vote_account, CommissionKind::BlockRevenue);
1775
1776        // Should pass - InflationRewards kind.
1777        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionCollector(
1778            CommissionKind::InflationRewards,
1779        ))
1780        .unwrap();
1781        let accounts = process_instruction(
1782            features,
1783            &instruction_data,
1784            transaction_accounts.clone(),
1785            instruction_accounts.clone(),
1786            Ok(()),
1787        );
1788        assert_eq!(
1789            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
1790            new_collector_pubkey,
1791        );
1792        assert_eq!(
1793            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
1794            original_block_revenue_collector, // Unchanged
1795        );
1796
1797        // Should pass - BlockRevenue kind.
1798        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionCollector(
1799            CommissionKind::BlockRevenue,
1800        ))
1801        .unwrap();
1802        let accounts = process_instruction(
1803            features,
1804            &instruction_data,
1805            transaction_accounts.clone(),
1806            instruction_accounts.clone(),
1807            Ok(()),
1808        );
1809        assert_eq!(
1810            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
1811            original_inflation_collector, // Unchanged
1812        );
1813        assert_eq!(
1814            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
1815            new_collector_pubkey,
1816        );
1817
1818        // Should pass - setting collector to vote account (InflationRewards).
1819        let vote_as_collector_instruction_accounts = vec![
1820            AccountMeta {
1821                pubkey: vote_pubkey,
1822                is_signer: false,
1823                is_writable: true,
1824            },
1825            AccountMeta {
1826                pubkey: vote_pubkey, // Collector is the vote account.
1827                is_signer: false,
1828                is_writable: true,
1829            },
1830            AccountMeta {
1831                pubkey: authorized_withdrawer,
1832                is_signer: true,
1833                is_writable: false,
1834            },
1835        ];
1836        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionCollector(
1837            CommissionKind::InflationRewards,
1838        ))
1839        .unwrap();
1840        let accounts = process_instruction(
1841            features,
1842            &instruction_data,
1843            transaction_accounts.clone(),
1844            vote_as_collector_instruction_accounts.clone(),
1845            Ok(()),
1846        );
1847        assert_eq!(
1848            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
1849            vote_pubkey
1850        );
1851        assert_eq!(
1852            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
1853            original_block_revenue_collector, // Unchanged
1854        );
1855
1856        // Should pass - setting collector to vote account (BlockRevenue).
1857        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionCollector(
1858            CommissionKind::BlockRevenue,
1859        ))
1860        .unwrap();
1861        let accounts = process_instruction(
1862            features,
1863            &instruction_data,
1864            transaction_accounts.clone(),
1865            vote_as_collector_instruction_accounts,
1866            Ok(()),
1867        );
1868        assert_eq!(
1869            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
1870            original_inflation_collector, // Unchanged
1871        );
1872        assert_eq!(
1873            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
1874            vote_pubkey
1875        );
1876
1877        // Should pass - all three accounts can alias.
1878        let aliased_vote_account =
1879            create_test_account_with_provided_authorized(&vote_pubkey, &vote_pubkey);
1880        let aliased_original_inflation_collector =
1881            get_commission_collector(&aliased_vote_account, CommissionKind::InflationRewards);
1882        let aliased_original_block_revenue_collector =
1883            get_commission_collector(&aliased_vote_account, CommissionKind::BlockRevenue);
1884        let aliased_transaction_accounts = vec![
1885            (vote_pubkey, aliased_vote_account),
1886            (sysvar::rent::id(), create_sysvar_account(&rent)),
1887        ];
1888        let aliased_instruction_accounts = vec![
1889            AccountMeta {
1890                pubkey: vote_pubkey, // Vote account
1891                is_signer: false,
1892                is_writable: true,
1893            },
1894            AccountMeta {
1895                pubkey: vote_pubkey, // New collector
1896                is_signer: false,
1897                is_writable: true,
1898            },
1899            AccountMeta {
1900                pubkey: vote_pubkey, // Authorized withdrawer
1901                is_signer: true,     // (Signer)
1902                is_writable: false,
1903            },
1904        ];
1905
1906        // InflationRewards (triple alias)
1907        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionCollector(
1908            CommissionKind::InflationRewards,
1909        ))
1910        .unwrap();
1911        let accounts = process_instruction(
1912            features,
1913            &instruction_data,
1914            aliased_transaction_accounts.clone(),
1915            aliased_instruction_accounts.clone(),
1916            Ok(()),
1917        );
1918        assert_eq!(
1919            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
1920            vote_pubkey
1921        );
1922        assert_eq!(
1923            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
1924            aliased_original_block_revenue_collector, // Unchanged
1925        );
1926
1927        // BlockRevenue (triple alias)
1928        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionCollector(
1929            CommissionKind::BlockRevenue,
1930        ))
1931        .unwrap();
1932        let accounts = process_instruction(
1933            features,
1934            &instruction_data,
1935            aliased_transaction_accounts,
1936            aliased_instruction_accounts,
1937            Ok(()),
1938        );
1939        assert_eq!(
1940            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
1941            aliased_original_inflation_collector, // Unchanged
1942        );
1943        assert_eq!(
1944            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
1945            vote_pubkey
1946        );
1947
1948        // Should fail - SIMD-0232 disabled.
1949        let instruction_data = wincode::serialize(&VoteInstruction::UpdateCommissionCollector(
1950            CommissionKind::InflationRewards,
1951        ))
1952        .unwrap();
1953        let accounts = process_instruction(
1954            VoteProgramFeatures {
1955                custom_commission_collector: false,
1956                ..Default::default()
1957            },
1958            &instruction_data,
1959            transaction_accounts.clone(),
1960            instruction_accounts.clone(),
1961            Err(InstructionError::InvalidInstructionData),
1962        );
1963        assert_eq!(
1964            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
1965            original_inflation_collector, // Unchanged
1966        );
1967        assert_eq!(
1968            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
1969            original_block_revenue_collector, // Unchanged
1970        );
1971
1972        // Should fail - fewer than 3 account inputs (SIMD-0232).
1973        let too_few_instruction_accounts = vec![AccountMeta {
1974            pubkey: vote_pubkey,
1975            is_signer: false,
1976            is_writable: true,
1977        }];
1978        let accounts = process_instruction(
1979            features,
1980            &instruction_data,
1981            transaction_accounts.clone(),
1982            too_few_instruction_accounts,
1983            Err(InstructionError::MissingAccount),
1984        );
1985        assert_eq!(
1986            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
1987            original_inflation_collector, // Unchanged
1988        );
1989        assert_eq!(
1990            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
1991            original_block_revenue_collector, // Unchanged
1992        );
1993
1994        // Should fail - authorized withdrawer didn't sign.
1995        let mut unsigned_instruction_accounts = instruction_accounts.clone();
1996        unsigned_instruction_accounts[2].is_signer = false;
1997        let accounts = process_instruction(
1998            features,
1999            &instruction_data,
2000            transaction_accounts.clone(),
2001            unsigned_instruction_accounts,
2002            Err(InstructionError::MissingRequiredSignature),
2003        );
2004        assert_eq!(
2005            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
2006            original_inflation_collector
2007        );
2008        assert_eq!(
2009            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
2010            original_block_revenue_collector, // Unchanged
2011        );
2012
2013        // Should fail - wrong signer (not the authorized withdrawer).
2014        let wrong_signer = Pubkey::new_unique();
2015        let mut wrong_signer_transaction_accounts = transaction_accounts.clone();
2016        wrong_signer_transaction_accounts.push((wrong_signer, AccountSharedData::default()));
2017        let wrong_signer_instruction_accounts = vec![
2018            AccountMeta {
2019                pubkey: vote_pubkey,
2020                is_signer: false,
2021                is_writable: true,
2022            },
2023            AccountMeta {
2024                pubkey: new_collector_pubkey,
2025                is_signer: false,
2026                is_writable: true,
2027            },
2028            AccountMeta {
2029                pubkey: wrong_signer,
2030                is_signer: true,
2031                is_writable: false,
2032            },
2033        ];
2034        let accounts = process_instruction(
2035            features,
2036            &instruction_data,
2037            wrong_signer_transaction_accounts,
2038            wrong_signer_instruction_accounts,
2039            Err(InstructionError::MissingRequiredSignature),
2040        );
2041        assert_eq!(
2042            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
2043            original_inflation_collector
2044        );
2045        assert_eq!(
2046            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
2047            original_block_revenue_collector, // Unchanged
2048        );
2049
2050        // Should fail - new collector not system program owned.
2051        let non_system_owner = Pubkey::new_unique();
2052        let non_system_collector_pubkey = Pubkey::new_unique();
2053        let non_system_collector_account =
2054            AccountSharedData::new(collector_lamports, 0, &non_system_owner);
2055        let mut non_system_transaction_accounts = transaction_accounts.clone();
2056        non_system_transaction_accounts[1] =
2057            (non_system_collector_pubkey, non_system_collector_account);
2058        let non_system_instruction_accounts = vec![
2059            AccountMeta {
2060                pubkey: vote_pubkey,
2061                is_signer: false,
2062                is_writable: true,
2063            },
2064            AccountMeta {
2065                pubkey: non_system_collector_pubkey,
2066                is_signer: false,
2067                is_writable: true,
2068            },
2069            AccountMeta {
2070                pubkey: authorized_withdrawer,
2071                is_signer: true,
2072                is_writable: false,
2073            },
2074        ];
2075        let accounts = process_instruction(
2076            features,
2077            &instruction_data,
2078            non_system_transaction_accounts,
2079            non_system_instruction_accounts,
2080            Err(InstructionError::InvalidAccountOwner),
2081        );
2082        assert_eq!(
2083            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
2084            original_inflation_collector
2085        );
2086        assert_eq!(
2087            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
2088            original_block_revenue_collector, // Unchanged
2089        );
2090
2091        // Should fail - new collector not rent-exempt.
2092        let not_rent_exempt_collector_pubkey = Pubkey::new_unique();
2093        let not_rent_exempt_collector_account =
2094            AccountSharedData::new(0, 0, &solana_sdk_ids::system_program::id()); // 0 lamports
2095        let mut not_rent_exempt_transaction_accounts = transaction_accounts.clone();
2096        not_rent_exempt_transaction_accounts[1] = (
2097            not_rent_exempt_collector_pubkey,
2098            not_rent_exempt_collector_account,
2099        );
2100        let not_rent_exempt_instruction_accounts = vec![
2101            AccountMeta {
2102                pubkey: vote_pubkey,
2103                is_signer: false,
2104                is_writable: true,
2105            },
2106            AccountMeta {
2107                pubkey: not_rent_exempt_collector_pubkey,
2108                is_signer: false,
2109                is_writable: true,
2110            },
2111            AccountMeta {
2112                pubkey: authorized_withdrawer,
2113                is_signer: true,
2114                is_writable: false,
2115            },
2116        ];
2117        let accounts = process_instruction(
2118            features,
2119            &instruction_data,
2120            not_rent_exempt_transaction_accounts,
2121            not_rent_exempt_instruction_accounts,
2122            Err(InstructionError::InsufficientFunds),
2123        );
2124        assert_eq!(
2125            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
2126            original_inflation_collector
2127        );
2128        assert_eq!(
2129            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
2130            original_block_revenue_collector, // Unchanged
2131        );
2132
2133        // Should fail - new collector not writable (reserved account check).
2134        let mut not_writable_instruction_accounts = instruction_accounts;
2135        not_writable_instruction_accounts[1].is_writable = false;
2136        let accounts = process_instruction(
2137            features,
2138            &instruction_data,
2139            transaction_accounts,
2140            not_writable_instruction_accounts,
2141            Err(InstructionError::InvalidArgument),
2142        );
2143        assert_eq!(
2144            get_commission_collector(&accounts[0], CommissionKind::InflationRewards),
2145            original_inflation_collector
2146        );
2147        assert_eq!(
2148            get_commission_collector(&accounts[0], CommissionKind::BlockRevenue),
2149            original_block_revenue_collector, // Unchanged
2150        );
2151    }
2152
2153    #[test]
2154    fn test_vote_signature() {
2155        let (vote_pubkey, vote_account) = create_test_account();
2156        let (vote, instruction_datas) = create_serialized_votes();
2157        let slot_hashes = SlotHashes::new(&[SlotHash::new(*vote.slots.last().unwrap(), vote.hash)]);
2158        let slot_hashes_account = create_sysvar_account(&slot_hashes);
2159        let mut instruction_accounts = vec![
2160            AccountMeta {
2161                pubkey: vote_pubkey,
2162                is_signer: true,
2163                is_writable: true,
2164            },
2165            AccountMeta {
2166                pubkey: sysvar::slot_hashes::id(),
2167                is_signer: false,
2168                is_writable: false,
2169            },
2170            AccountMeta {
2171                pubkey: sysvar::clock::id(),
2172                is_signer: false,
2173                is_writable: false,
2174            },
2175        ];
2176
2177        let features = VoteProgramFeatures {
2178            ..Default::default()
2179        };
2180
2181        for (instruction_data, is_tower_sync) in instruction_datas {
2182            let mut transaction_accounts = vec![
2183                (vote_pubkey, vote_account.clone()),
2184                (sysvar::slot_hashes::id(), slot_hashes_account.clone()),
2185                (sysvar::clock::id(), create_default_clock_account()),
2186            ];
2187
2188            let error = |err| {
2189                if !is_tower_sync {
2190                    Err(InstructionError::InvalidInstructionData)
2191                } else {
2192                    Err(err)
2193                }
2194            };
2195
2196            // should fail, unsigned
2197            instruction_accounts[0].is_signer = false;
2198            process_instruction(
2199                features,
2200                &instruction_data,
2201                transaction_accounts.clone(),
2202                instruction_accounts.clone(),
2203                error(InstructionError::MissingRequiredSignature),
2204            );
2205            instruction_accounts[0].is_signer = true;
2206
2207            // should pass
2208            let accounts = process_instruction(
2209                features,
2210                &instruction_data,
2211                transaction_accounts.clone(),
2212                instruction_accounts.clone(),
2213                if is_tower_sync {
2214                    Ok(())
2215                } else {
2216                    Err(InstructionError::InvalidInstructionData)
2217                },
2218            );
2219            if is_tower_sync {
2220                let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
2221                let expected_lockout = Lockout::new(*vote.slots.last().unwrap());
2222                assert_eq!(vote_state.votes().len(), 1);
2223                assert_eq!(vote_state.votes()[0].lockout, expected_lockout);
2224                assert_eq!(vote_state.credits(), 0);
2225            }
2226
2227            // should fail, wrong hash
2228            transaction_accounts[1] = (
2229                sysvar::slot_hashes::id(),
2230                create_sysvar_account(&SlotHashes::new(&[SlotHash::new(
2231                    *vote.slots.last().unwrap(),
2232                    solana_sha256_hasher::hash(&[0u8]),
2233                )])),
2234            );
2235            process_instruction(
2236                features,
2237                &instruction_data,
2238                transaction_accounts.clone(),
2239                instruction_accounts.clone(),
2240                error(VoteError::SlotHashMismatch.into()),
2241            );
2242
2243            // should fail, wrong slot
2244            transaction_accounts[1] = (
2245                sysvar::slot_hashes::id(),
2246                create_sysvar_account(&SlotHashes::new(&[SlotHash::new(0, vote.hash)])),
2247            );
2248            process_instruction(
2249                features,
2250                &instruction_data,
2251                transaction_accounts.clone(),
2252                instruction_accounts.clone(),
2253                error(VoteError::SlotsMismatch.into()),
2254            );
2255
2256            // should fail, empty slot_hashes
2257            transaction_accounts[1] = (
2258                sysvar::slot_hashes::id(),
2259                create_sysvar_account(&SlotHashes::new(&[])),
2260            );
2261            process_instruction(
2262                features,
2263                &instruction_data,
2264                transaction_accounts.clone(),
2265                instruction_accounts.clone(),
2266                error(VoteError::SlotsMismatch.into()),
2267            );
2268            transaction_accounts[1] = (sysvar::slot_hashes::id(), slot_hashes_account.clone());
2269
2270            // should fail, uninitialized
2271            let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
2272            transaction_accounts[0] = (vote_pubkey, vote_account);
2273            process_instruction(
2274                features,
2275                &instruction_data,
2276                transaction_accounts.clone(),
2277                instruction_accounts.clone(),
2278                error(InstructionError::InvalidAccountData),
2279            );
2280        }
2281    }
2282
2283    #[test_matrix([false, true])]
2284    fn test_authorize_voter(bls_pubkey_management_in_vote_account: bool) {
2285        let (vote_pubkey, vote_account) = create_test_account();
2286        let authorized_voter_pubkey = solana_pubkey::new_rand();
2287        let clock = Clock {
2288            epoch: 1,
2289            leader_schedule_epoch: 2,
2290            ..Clock::default()
2291        };
2292        let clock_account = create_sysvar_account(&clock);
2293        let instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2294            authorized_voter_pubkey,
2295            VoteAuthorize::Voter,
2296        ))
2297        .unwrap();
2298
2299        let mut transaction_accounts = vec![
2300            (vote_pubkey, vote_account.clone()),
2301            (sysvar::clock::id(), clock_account.clone()),
2302            (authorized_voter_pubkey, AccountSharedData::default()),
2303        ];
2304        let mut instruction_accounts = vec![
2305            AccountMeta {
2306                pubkey: vote_pubkey,
2307                is_signer: true,
2308                is_writable: true,
2309            },
2310            AccountMeta {
2311                pubkey: sysvar::clock::id(),
2312                is_signer: false,
2313                is_writable: false,
2314            },
2315        ];
2316
2317        let features = VoteProgramFeatures {
2318            bls_pubkey_management_in_vote_account,
2319            ..Default::default()
2320        };
2321
2322        // processing incompatible instruction should fail
2323        if bls_pubkey_management_in_vote_account {
2324            // If both features are enabled, the old instruction should be rejected
2325            process_instruction(
2326                features,
2327                &instruction_data,
2328                vec![
2329                    (vote_pubkey, vote_account),
2330                    (sysvar::clock::id(), clock_account),
2331                    (authorized_voter_pubkey, AccountSharedData::default()),
2332                ],
2333                instruction_accounts.clone(),
2334                Err(InstructionError::InvalidInstructionData),
2335            );
2336            return;
2337        } else {
2338            // If either feature is disabled, the new instruction should be rejected
2339            let (bls_pubkey, bls_proof_of_possession) =
2340                create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
2341            let bad_instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2342                authorized_voter_pubkey,
2343                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
2344                    bls_pubkey,
2345                    bls_proof_of_possession,
2346                }),
2347            ))
2348            .unwrap();
2349            process_instruction(
2350                features,
2351                &bad_instruction_data,
2352                vec![
2353                    (vote_pubkey, vote_account),
2354                    (sysvar::clock::id(), clock_account),
2355                    (authorized_voter_pubkey, AccountSharedData::default()),
2356                ],
2357                instruction_accounts.clone(),
2358                Err(InstructionError::InvalidInstructionData),
2359            );
2360        }
2361
2362        // should fail, unsigned
2363        instruction_accounts[0].is_signer = false;
2364        process_instruction(
2365            features,
2366            &instruction_data,
2367            transaction_accounts.clone(),
2368            instruction_accounts.clone(),
2369            Err(InstructionError::MissingRequiredSignature),
2370        );
2371        instruction_accounts[0].is_signer = true;
2372
2373        // should pass
2374        let accounts = process_instruction(
2375            features,
2376            &instruction_data,
2377            transaction_accounts.clone(),
2378            instruction_accounts.clone(),
2379            Ok(()),
2380        );
2381
2382        // should fail, already set an authorized voter earlier for leader_schedule_epoch == 2
2383        transaction_accounts[0] = (vote_pubkey, accounts[0].clone());
2384        process_instruction(
2385            features,
2386            &instruction_data,
2387            transaction_accounts.clone(),
2388            instruction_accounts.clone(),
2389            Err(VoteError::TooSoonToReauthorize.into()),
2390        );
2391
2392        // should pass, verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;)
2393        instruction_accounts[0].is_signer = false;
2394        instruction_accounts.push(AccountMeta {
2395            pubkey: authorized_voter_pubkey,
2396            is_signer: true,
2397            is_writable: false,
2398        });
2399        let clock = Clock {
2400            // The authorized voter was set when leader_schedule_epoch == 2, so will
2401            // take effect when epoch == 3
2402            epoch: 3,
2403            leader_schedule_epoch: 4,
2404            ..Clock::default()
2405        };
2406        let clock_account = create_sysvar_account(&clock);
2407        transaction_accounts[1] = (sysvar::clock::id(), clock_account);
2408        process_instruction(
2409            features,
2410            &instruction_data,
2411            transaction_accounts.clone(),
2412            instruction_accounts.clone(),
2413            Ok(()),
2414        );
2415        instruction_accounts[0].is_signer = true;
2416        instruction_accounts.pop();
2417
2418        // should fail, not signed by authorized voter
2419        let (vote, instruction_datas) = create_serialized_votes();
2420        let slot_hashes = SlotHashes::new(&[SlotHash::new(*vote.slots.last().unwrap(), vote.hash)]);
2421        let slot_hashes_account = create_sysvar_account(&slot_hashes);
2422        transaction_accounts.push((sysvar::slot_hashes::id(), slot_hashes_account));
2423        instruction_accounts.insert(
2424            1,
2425            AccountMeta {
2426                pubkey: sysvar::slot_hashes::id(),
2427                is_signer: false,
2428                is_writable: false,
2429            },
2430        );
2431        let mut authorized_instruction_accounts = instruction_accounts.clone();
2432        authorized_instruction_accounts.push(AccountMeta {
2433            pubkey: authorized_voter_pubkey,
2434            is_signer: true,
2435            is_writable: false,
2436        });
2437
2438        for (instruction_data, is_tower_sync) in instruction_datas {
2439            process_instruction(
2440                features,
2441                &instruction_data,
2442                transaction_accounts.clone(),
2443                instruction_accounts.clone(),
2444                Err(if is_tower_sync {
2445                    InstructionError::MissingRequiredSignature
2446                } else {
2447                    InstructionError::InvalidInstructionData
2448                }),
2449            );
2450
2451            // should pass, signed by authorized voter
2452            process_instruction(
2453                features,
2454                &instruction_data,
2455                transaction_accounts.clone(),
2456                authorized_instruction_accounts.clone(),
2457                if is_tower_sync {
2458                    Ok(())
2459                } else {
2460                    Err(InstructionError::InvalidInstructionData)
2461                },
2462            );
2463        }
2464    }
2465
2466    #[test_matrix([false, true])]
2467    fn test_authorize_voter_with_bls(bls_pubkey_management_in_vote_account: bool) {
2468        agave_logger::setup();
2469        let (vote_pubkey, vote_account) = create_test_account();
2470        let authorized_voter_pubkey = solana_pubkey::new_rand();
2471        let clock = Clock {
2472            epoch: 1,
2473            leader_schedule_epoch: 2,
2474            ..Clock::default()
2475        };
2476        let clock_account = create_sysvar_account(&clock);
2477        let (bls_pubkey, bls_proof_of_possession) =
2478            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
2479        let instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2480            authorized_voter_pubkey,
2481            VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
2482                bls_pubkey,
2483                bls_proof_of_possession,
2484            }),
2485        ))
2486        .unwrap();
2487
2488        let mut transaction_accounts = vec![
2489            (vote_pubkey, vote_account.clone()),
2490            (sysvar::clock::id(), clock_account.clone()),
2491            (authorized_voter_pubkey, AccountSharedData::default()),
2492        ];
2493        let mut instruction_accounts = vec![
2494            AccountMeta {
2495                pubkey: vote_pubkey,
2496                is_signer: true,
2497                is_writable: true,
2498            },
2499            AccountMeta {
2500                pubkey: sysvar::clock::id(),
2501                is_signer: false,
2502                is_writable: false,
2503            },
2504        ];
2505
2506        let features = VoteProgramFeatures {
2507            bls_pubkey_management_in_vote_account,
2508            ..Default::default()
2509        };
2510
2511        // processing incompatible instruction should fail
2512        if bls_pubkey_management_in_vote_account {
2513            // If both features are enabled, the old instruction should be accepted when
2514            // the account does not have a BLS key.
2515            let (new_vote_pubkey, vote_account_no_bls_key) = create_test_account_no_bls_key();
2516            let new_authorized_voter_pubkey = solana_pubkey::new_rand();
2517            let old_instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2518                new_authorized_voter_pubkey,
2519                VoteAuthorize::Voter,
2520            ))
2521            .unwrap();
2522            process_instruction(
2523                features,
2524                &old_instruction_data,
2525                vec![
2526                    (new_vote_pubkey, vote_account_no_bls_key),
2527                    (sysvar::clock::id(), clock_account.clone()),
2528                    (new_authorized_voter_pubkey, AccountSharedData::default()),
2529                ],
2530                vec![
2531                    AccountMeta {
2532                        pubkey: new_vote_pubkey,
2533                        is_signer: true,
2534                        is_writable: true,
2535                    },
2536                    AccountMeta {
2537                        pubkey: sysvar::clock::id(),
2538                        is_signer: false,
2539                        is_writable: false,
2540                    },
2541                ],
2542                Ok(()),
2543            );
2544            // However, once the BLS key is set, the old instruction should be rejected
2545            let (new_vote_pubkey, vote_account_with_bls_key) = create_test_account();
2546            let new_authorized_voter_pubkey = solana_pubkey::new_rand();
2547            let old_instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2548                new_authorized_voter_pubkey,
2549                VoteAuthorize::Voter,
2550            ))
2551            .unwrap();
2552            process_instruction(
2553                features,
2554                &old_instruction_data,
2555                vec![
2556                    (new_vote_pubkey, vote_account_with_bls_key),
2557                    (sysvar::clock::id(), clock_account),
2558                    (new_authorized_voter_pubkey, AccountSharedData::default()),
2559                ],
2560                vec![
2561                    AccountMeta {
2562                        pubkey: new_vote_pubkey,
2563                        is_signer: true,
2564                        is_writable: true,
2565                    },
2566                    AccountMeta {
2567                        pubkey: sysvar::clock::id(),
2568                        is_signer: false,
2569                        is_writable: false,
2570                    },
2571                ],
2572                Err(InstructionError::InvalidInstructionData),
2573            );
2574        } else {
2575            // If either feature is disabled, the new instruction should be rejected
2576            let (bls_pubkey, bls_proof_of_possession) =
2577                create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
2578            let bad_instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2579                authorized_voter_pubkey,
2580                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
2581                    bls_pubkey,
2582                    bls_proof_of_possession,
2583                }),
2584            ))
2585            .unwrap();
2586
2587            process_instruction(
2588                features,
2589                &bad_instruction_data,
2590                vec![
2591                    (vote_pubkey, vote_account),
2592                    (sysvar::clock::id(), clock_account),
2593                    (authorized_voter_pubkey, AccountSharedData::default()),
2594                ],
2595                instruction_accounts.clone(),
2596                Err(InstructionError::InvalidInstructionData),
2597            );
2598            return;
2599        }
2600
2601        // should fail, unsigned
2602        instruction_accounts[0].is_signer = false;
2603        process_instruction_with_cu_check(
2604            features,
2605            &instruction_data,
2606            transaction_accounts.clone(),
2607            instruction_accounts.clone(),
2608            Err(InstructionError::MissingRequiredSignature),
2609            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
2610        );
2611        instruction_accounts[0].is_signer = true;
2612
2613        // should pass
2614        let accounts = process_instruction_with_cu_check(
2615            features,
2616            &instruction_data,
2617            transaction_accounts.clone(),
2618            instruction_accounts.clone(),
2619            Ok(()),
2620            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
2621        );
2622
2623        // should fail, already set an authorized voter earlier for leader_schedule_epoch == 2
2624        transaction_accounts[0] = (vote_pubkey, accounts[0].clone());
2625        process_instruction_with_cu_check(
2626            features,
2627            &instruction_data,
2628            transaction_accounts.clone(),
2629            instruction_accounts.clone(),
2630            Err(VoteError::TooSoonToReauthorize.into()),
2631            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
2632        );
2633
2634        // should pass, verify authorized_voter_pubkey can authorize authorized_voter_pubkey ;)
2635        instruction_accounts[0].is_signer = false;
2636        instruction_accounts.push(AccountMeta {
2637            pubkey: authorized_voter_pubkey,
2638            is_signer: true,
2639            is_writable: false,
2640        });
2641        let clock = Clock {
2642            // The authorized voter was set when leader_schedule_epoch == 2, so will
2643            // take effect when epoch == 3
2644            epoch: 3,
2645            leader_schedule_epoch: 4,
2646            ..Clock::default()
2647        };
2648        let clock_account = create_sysvar_account(&clock);
2649        transaction_accounts[1] = (sysvar::clock::id(), clock_account);
2650        process_instruction_with_cu_check(
2651            features,
2652            &instruction_data,
2653            transaction_accounts.clone(),
2654            instruction_accounts.clone(),
2655            Ok(()),
2656            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
2657        );
2658        instruction_accounts[0].is_signer = true;
2659        instruction_accounts.pop();
2660
2661        // should fail, not signed by authorized voter
2662        let (vote, instruction_datas) = create_serialized_votes();
2663        let slot_hashes = SlotHashes::new(&[SlotHash::new(*vote.slots.last().unwrap(), vote.hash)]);
2664        let slot_hashes_account = create_sysvar_account(&slot_hashes);
2665        transaction_accounts.push((sysvar::slot_hashes::id(), slot_hashes_account));
2666        instruction_accounts.insert(
2667            1,
2668            AccountMeta {
2669                pubkey: sysvar::slot_hashes::id(),
2670                is_signer: false,
2671                is_writable: false,
2672            },
2673        );
2674        let mut authorized_instruction_accounts = instruction_accounts.clone();
2675        authorized_instruction_accounts.push(AccountMeta {
2676            pubkey: authorized_voter_pubkey,
2677            is_signer: true,
2678            is_writable: false,
2679        });
2680
2681        for (instruction_data, is_tower_sync) in instruction_datas {
2682            process_instruction(
2683                features,
2684                &instruction_data,
2685                transaction_accounts.clone(),
2686                instruction_accounts.clone(),
2687                Err(if is_tower_sync {
2688                    InstructionError::MissingRequiredSignature
2689                } else {
2690                    InstructionError::InvalidInstructionData
2691                }),
2692            );
2693
2694            // should pass, signed by authorized voter
2695            process_instruction(
2696                features,
2697                &instruction_data,
2698                transaction_accounts.clone(),
2699                authorized_instruction_accounts.clone(),
2700                if is_tower_sync {
2701                    Ok(())
2702                } else {
2703                    Err(InstructionError::InvalidInstructionData)
2704                },
2705            );
2706        }
2707    }
2708
2709    #[test]
2710    fn test_authorize_voter_with_bls_bad_proof_of_possession() {
2711        let (vote_pubkey, vote_account) = create_test_account();
2712        let authorized_voter_pubkey = solana_pubkey::new_rand();
2713        let clock = Clock {
2714            epoch: 1,
2715            leader_schedule_epoch: 2,
2716            ..Clock::default()
2717        };
2718        let clock_account = create_sysvar_account(&clock);
2719        let transaction_accounts = vec![
2720            (vote_pubkey, vote_account),
2721            (sysvar::clock::id(), clock_account),
2722            (authorized_voter_pubkey, AccountSharedData::default()),
2723        ];
2724        let instruction_accounts = vec![
2725            AccountMeta {
2726                pubkey: vote_pubkey,
2727                is_signer: true,
2728                is_writable: true,
2729            },
2730            AccountMeta {
2731                pubkey: sysvar::clock::id(),
2732                is_signer: false,
2733                is_writable: false,
2734            },
2735        ];
2736
2737        // Test that bad proof of possession fails authorization
2738        let instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2739            authorized_voter_pubkey,
2740            VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
2741                bls_pubkey: [1u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE],
2742                bls_proof_of_possession: [2u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE],
2743            }),
2744        ))
2745        .unwrap();
2746        process_instruction_with_cu_check(
2747            VoteProgramFeatures {
2748                bls_pubkey_management_in_vote_account: true,
2749                ..Default::default()
2750            },
2751            &instruction_data,
2752            transaction_accounts,
2753            instruction_accounts,
2754            Err(InstructionError::InvalidArgument),
2755            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
2756        );
2757    }
2758
2759    // Tests (and essentially documents) BLS pubkey rotation characteristics
2760    // by testing the end-to-end behavior of `Authorize::VoterWithBls` in the
2761    // program.
2762    //
2763    // All rotations reuse the existing authorized voter to simulate an
2764    // operator's desire to only change their BLS pubkey.
2765    //
2766    // TL;DR: Since authorized voter rotations are capped at once per epoch,
2767    // so too are BLS pubkey rotations.
2768    #[test]
2769    fn test_bls_pubkey_rotation() {
2770        let features = VoteProgramFeatures {
2771            bls_pubkey_management_in_vote_account: true,
2772            ..Default::default()
2773        };
2774
2775        // Start out with no BLS pubkey set.
2776        // Authorized voter is already set to vote pubkey.
2777        let (vote_pubkey, vote_account) = create_test_account_no_bls_key();
2778        let authorized_voter = vote_pubkey;
2779
2780        // Two distinct BLS keypairs for rotation.
2781        let (bls_1, pop_1) = create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
2782        let (bls_2, pop_2) = create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
2783
2784        let ix_accounts = vec![
2785            AccountMeta {
2786                pubkey: vote_pubkey,
2787                is_signer: true,
2788                is_writable: true,
2789            },
2790            AccountMeta {
2791                pubkey: sysvar::clock::id(),
2792                is_signer: false,
2793                is_writable: false,
2794            },
2795        ];
2796
2797        // Epoch: 1
2798        // Leader Schedule Epoch: 2
2799        //   Rotation schedules for target epoch 3
2800        //   BLS pubkey is set immediately
2801        let clock_epoch_1 = create_sysvar_account(&Clock {
2802            epoch: 1,
2803            leader_schedule_epoch: 2,
2804            ..Clock::default()
2805        });
2806        let accounts = process_instruction_with_cu_check(
2807            features,
2808            &wincode::serialize(&VoteInstruction::Authorize(
2809                authorized_voter,
2810                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
2811                    bls_pubkey: bls_1,
2812                    bls_proof_of_possession: pop_1,
2813                }),
2814            ))
2815            .unwrap(),
2816            vec![
2817                (vote_pubkey, vote_account),
2818                (sysvar::clock::id(), clock_epoch_1.clone()),
2819            ],
2820            ix_accounts.clone(),
2821            Ok(()),
2822            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
2823        );
2824        let v4 = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
2825        assert_eq!(v4.as_ref_v4().bls_pubkey_compressed, Some(bls_1));
2826
2827        // == Same epoch again ==
2828        // Epoch: 1
2829        // Leader Schedule Epoch: 2
2830        //   Rotation fails with `TooSoonToReauthorize`
2831        //   BLS pubkey is NOT set
2832        //   34,500 CUs still charged
2833        let accounts = process_instruction_with_cu_check(
2834            features,
2835            &wincode::serialize(&VoteInstruction::Authorize(
2836                authorized_voter,
2837                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
2838                    bls_pubkey: bls_2,
2839                    bls_proof_of_possession: pop_2,
2840                }),
2841            ))
2842            .unwrap(),
2843            vec![
2844                (vote_pubkey, accounts[0].clone()),
2845                (sysvar::clock::id(), clock_epoch_1),
2846            ],
2847            ix_accounts.clone(),
2848            Err(VoteError::TooSoonToReauthorize.into()),
2849            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
2850        );
2851        let v4 = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
2852        assert_eq!(v4.as_ref_v4().bls_pubkey_compressed, Some(bls_1)); // <-- Still BLS key 1
2853
2854        // == New epoch ==
2855        // Epoch: 2
2856        // Leader Schedule Epoch: 3
2857        //   Rotation schedules for target epoch 4
2858        //   BLS pubkey is set immediately
2859        let clock_epoch_2 = create_sysvar_account(&Clock {
2860            epoch: 2,
2861            leader_schedule_epoch: 3,
2862            ..Clock::default()
2863        });
2864        let accounts = process_instruction_with_cu_check(
2865            features,
2866            &wincode::serialize(&VoteInstruction::Authorize(
2867                authorized_voter,
2868                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
2869                    bls_pubkey: bls_2,
2870                    bls_proof_of_possession: pop_2,
2871                }),
2872            ))
2873            .unwrap(),
2874            vec![
2875                (vote_pubkey, accounts[0].clone()),
2876                (sysvar::clock::id(), clock_epoch_2),
2877            ],
2878            ix_accounts.clone(),
2879            Ok(()),
2880            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
2881        );
2882        let v4 = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
2883        assert_eq!(v4.as_ref_v4().bls_pubkey_compressed, Some(bls_2)); // <-- Now BLS key 2
2884    }
2885
2886    #[test]
2887    fn test_authorize_withdrawer() {
2888        let (vote_pubkey, vote_account) = create_test_account();
2889        let authorized_withdrawer_pubkey = solana_pubkey::new_rand();
2890        let instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2891            authorized_withdrawer_pubkey,
2892            VoteAuthorize::Withdrawer,
2893        ))
2894        .unwrap();
2895        let mut transaction_accounts = vec![
2896            (vote_pubkey, vote_account),
2897            (sysvar::clock::id(), create_default_clock_account()),
2898            (authorized_withdrawer_pubkey, AccountSharedData::default()),
2899        ];
2900        let mut instruction_accounts = vec![
2901            AccountMeta {
2902                pubkey: vote_pubkey,
2903                is_signer: true,
2904                is_writable: true,
2905            },
2906            AccountMeta {
2907                pubkey: sysvar::clock::id(),
2908                is_signer: false,
2909                is_writable: false,
2910            },
2911        ];
2912
2913        let features = VoteProgramFeatures {
2914            ..Default::default()
2915        };
2916
2917        // should fail, unsigned
2918        instruction_accounts[0].is_signer = false;
2919        process_instruction(
2920            features,
2921            &instruction_data,
2922            transaction_accounts.clone(),
2923            instruction_accounts.clone(),
2924            Err(InstructionError::MissingRequiredSignature),
2925        );
2926        instruction_accounts[0].is_signer = true;
2927
2928        // should pass
2929        let accounts = process_instruction(
2930            features,
2931            &instruction_data,
2932            transaction_accounts.clone(),
2933            instruction_accounts.clone(),
2934            Ok(()),
2935        );
2936
2937        // should pass, verify authorized_withdrawer can authorize authorized_withdrawer ;)
2938        instruction_accounts[0].is_signer = false;
2939        instruction_accounts.push(AccountMeta {
2940            pubkey: authorized_withdrawer_pubkey,
2941            is_signer: true,
2942            is_writable: false,
2943        });
2944        transaction_accounts[0] = (vote_pubkey, accounts[0].clone());
2945        process_instruction(
2946            features,
2947            &instruction_data,
2948            transaction_accounts.clone(),
2949            instruction_accounts.clone(),
2950            Ok(()),
2951        );
2952
2953        // should pass, verify authorized_withdrawer can authorize a new authorized_voter
2954        let authorized_voter_pubkey = solana_pubkey::new_rand();
2955        transaction_accounts.push((authorized_voter_pubkey, AccountSharedData::default()));
2956        let instruction_data = wincode::serialize(&VoteInstruction::Authorize(
2957            authorized_voter_pubkey,
2958            VoteAuthorize::Voter,
2959        ))
2960        .unwrap();
2961        process_instruction(
2962            features,
2963            &instruction_data,
2964            transaction_accounts.clone(),
2965            instruction_accounts.clone(),
2966            Ok(()),
2967        );
2968    }
2969
2970    #[test]
2971    fn test_vote_withdraw() {
2972        let (vote_pubkey, vote_account) = create_test_account();
2973        let lamports = vote_account.lamports();
2974        let authorized_withdrawer_pubkey = solana_pubkey::new_rand();
2975        let mut transaction_accounts = vec![
2976            (vote_pubkey, vote_account.clone()),
2977            (sysvar::clock::id(), create_default_clock_account()),
2978            (sysvar::rent::id(), create_default_rent_account()),
2979            (authorized_withdrawer_pubkey, AccountSharedData::default()),
2980        ];
2981        let mut instruction_accounts = vec![
2982            AccountMeta {
2983                pubkey: vote_pubkey,
2984                is_signer: true,
2985                is_writable: true,
2986            },
2987            AccountMeta {
2988                pubkey: sysvar::clock::id(),
2989                is_signer: false,
2990                is_writable: false,
2991            },
2992        ];
2993
2994        let features = VoteProgramFeatures {
2995            ..Default::default()
2996        };
2997
2998        // should pass, withdraw using authorized_withdrawer to authorized_withdrawer's account
2999        let accounts = process_instruction(
3000            features,
3001            &wincode::serialize(&VoteInstruction::Authorize(
3002                authorized_withdrawer_pubkey,
3003                VoteAuthorize::Withdrawer,
3004            ))
3005            .unwrap(),
3006            transaction_accounts.clone(),
3007            instruction_accounts.clone(),
3008            Ok(()),
3009        );
3010        instruction_accounts[0].is_signer = false;
3011        instruction_accounts[1] = AccountMeta {
3012            pubkey: authorized_withdrawer_pubkey,
3013            is_signer: true,
3014            is_writable: true,
3015        };
3016        transaction_accounts[0] = (vote_pubkey, accounts[0].clone());
3017        let accounts = process_instruction(
3018            features,
3019            &wincode::serialize(&VoteInstruction::Withdraw(lamports)).unwrap(),
3020            transaction_accounts.clone(),
3021            instruction_accounts.clone(),
3022            Ok(()),
3023        );
3024        assert_eq!(accounts[0].lamports(), 0);
3025        assert_eq!(accounts[3].lamports(), lamports);
3026        let post_state: VoteStateVersions = accounts[0].state().unwrap();
3027        // State has been deinitialized since balance is zero
3028        assert!(post_state.is_uninitialized());
3029
3030        // should fail, unsigned
3031        transaction_accounts[0] = (vote_pubkey, vote_account);
3032        process_instruction(
3033            features,
3034            &wincode::serialize(&VoteInstruction::Withdraw(lamports)).unwrap(),
3035            transaction_accounts.clone(),
3036            instruction_accounts.clone(),
3037            Err(InstructionError::MissingRequiredSignature),
3038        );
3039        instruction_accounts[0].is_signer = true;
3040
3041        // should pass
3042        process_instruction(
3043            features,
3044            &wincode::serialize(&VoteInstruction::Withdraw(lamports)).unwrap(),
3045            transaction_accounts.clone(),
3046            instruction_accounts.clone(),
3047            Ok(()),
3048        );
3049
3050        // should fail, insufficient funds
3051        process_instruction(
3052            features,
3053            &wincode::serialize(&VoteInstruction::Withdraw(lamports + 1)).unwrap(),
3054            transaction_accounts.clone(),
3055            instruction_accounts.clone(),
3056            Err(InstructionError::InsufficientFunds),
3057        );
3058
3059        // should pass, partial withdraw
3060        let withdraw_lamports = 42;
3061        let accounts = process_instruction(
3062            features,
3063            &wincode::serialize(&VoteInstruction::Withdraw(withdraw_lamports)).unwrap(),
3064            transaction_accounts,
3065            instruction_accounts,
3066            Ok(()),
3067        );
3068        assert_eq!(accounts[0].lamports(), lamports - withdraw_lamports);
3069        assert_eq!(accounts[3].lamports(), withdraw_lamports);
3070    }
3071
3072    #[test]
3073    fn test_vote_state_withdraw() {
3074        let authorized_withdrawer_pubkey = solana_pubkey::new_rand();
3075        let (vote_pubkey_1, vote_account_with_epoch_credits_1) =
3076            create_test_account_with_epoch_credits(&[2, 1]);
3077        let (vote_pubkey_2, vote_account_with_epoch_credits_2) =
3078            create_test_account_with_epoch_credits(&[2, 1, 3]);
3079        let clock = Clock {
3080            epoch: 3,
3081            ..Clock::default()
3082        };
3083        let clock_account = create_sysvar_account(&clock);
3084        let rent_sysvar = Rent::default();
3085        let minimum_balance = rent_sysvar
3086            .minimum_balance(vote_account_with_epoch_credits_1.data().len())
3087            .max(1);
3088        let lamports = vote_account_with_epoch_credits_1.lamports();
3089        let transaction_accounts = vec![
3090            (vote_pubkey_1, vote_account_with_epoch_credits_1),
3091            (vote_pubkey_2, vote_account_with_epoch_credits_2),
3092            (sysvar::clock::id(), clock_account),
3093            (sysvar::rent::id(), create_sysvar_account(&rent_sysvar)),
3094            (authorized_withdrawer_pubkey, AccountSharedData::default()),
3095        ];
3096        let mut instruction_accounts = vec![
3097            AccountMeta {
3098                pubkey: vote_pubkey_1,
3099                is_signer: true,
3100                is_writable: true,
3101            },
3102            AccountMeta {
3103                pubkey: authorized_withdrawer_pubkey,
3104                is_signer: false,
3105                is_writable: true,
3106            },
3107        ];
3108
3109        let features = VoteProgramFeatures {
3110            ..Default::default()
3111        };
3112
3113        // non rent exempt withdraw, with 0 credit epoch
3114        instruction_accounts[0].pubkey = vote_pubkey_1;
3115        process_instruction(
3116            features,
3117            &wincode::serialize(&VoteInstruction::Withdraw(lamports - minimum_balance + 1))
3118                .unwrap(),
3119            transaction_accounts.clone(),
3120            instruction_accounts.clone(),
3121            Err(InstructionError::InsufficientFunds),
3122        );
3123
3124        // non rent exempt withdraw, without 0 credit epoch
3125        instruction_accounts[0].pubkey = vote_pubkey_2;
3126        process_instruction(
3127            features,
3128            &wincode::serialize(&VoteInstruction::Withdraw(lamports - minimum_balance + 1))
3129                .unwrap(),
3130            transaction_accounts.clone(),
3131            instruction_accounts.clone(),
3132            Err(InstructionError::InsufficientFunds),
3133        );
3134
3135        // full withdraw, with 0 credit epoch
3136        instruction_accounts[0].pubkey = vote_pubkey_1;
3137        process_instruction(
3138            features,
3139            &wincode::serialize(&VoteInstruction::Withdraw(lamports)).unwrap(),
3140            transaction_accounts.clone(),
3141            instruction_accounts.clone(),
3142            Ok(()),
3143        );
3144
3145        // full withdraw, without 0 credit epoch
3146        instruction_accounts[0].pubkey = vote_pubkey_2;
3147        process_instruction(
3148            features,
3149            &wincode::serialize(&VoteInstruction::Withdraw(lamports)).unwrap(),
3150            transaction_accounts,
3151            instruction_accounts,
3152            Err(VoteError::ActiveVoteAccountClose.into()),
3153        );
3154    }
3155
3156    #[test]
3157    fn test_deinitialized_account_full_lifecycle_v4() {
3158        // Full lifecycle: withdraw all lamports to deinitialize a V4
3159        // account, verify instructions are rejected on the zeroed
3160        // account, then re-initialize and confirm no residual state.
3161        let (vote_pubkey, _authorized_voter, authorized_withdrawer, vote_account) =
3162            create_test_account_with_authorized();
3163        let lamports = vote_account.lamports();
3164
3165        let features = VoteProgramFeatures {
3166            ..Default::default()
3167        };
3168
3169        let recipient_pubkey = solana_pubkey::new_rand();
3170        let transaction_accounts = vec![
3171            (vote_pubkey, vote_account),
3172            (recipient_pubkey, AccountSharedData::default()),
3173            (authorized_withdrawer, AccountSharedData::default()),
3174            (sysvar::rent::id(), create_default_rent_account()),
3175            (sysvar::clock::id(), create_default_clock_account()),
3176        ];
3177        let instruction_accounts = vec![
3178            AccountMeta {
3179                pubkey: vote_pubkey,
3180                is_signer: false,
3181                is_writable: true,
3182            },
3183            AccountMeta {
3184                pubkey: recipient_pubkey,
3185                is_signer: false,
3186                is_writable: true,
3187            },
3188            AccountMeta {
3189                pubkey: authorized_withdrawer,
3190                is_signer: true,
3191                is_writable: false,
3192            },
3193        ];
3194
3195        // Withdraw all lamports to deinitialize.
3196        let accounts = process_instruction(
3197            features,
3198            &wincode::serialize(&VoteInstruction::Withdraw(lamports)).unwrap(),
3199            transaction_accounts,
3200            instruction_accounts,
3201            Ok(()),
3202        );
3203        let deinitialized_vote_account = &accounts[0];
3204
3205        // Account data should be all zeros.
3206        assert!(deinitialized_vote_account.data().iter().all(|&b| b == 0));
3207
3208        // Authorize should fail on the deinitialized account.
3209        let clock_account = create_sysvar_account(&Clock {
3210            epoch: 100,
3211            ..Clock::default()
3212        });
3213        process_instruction(
3214            features,
3215            &wincode::serialize(&VoteInstruction::Authorize(
3216                solana_pubkey::new_rand(),
3217                VoteAuthorize::Voter,
3218            ))
3219            .unwrap(),
3220            vec![
3221                (vote_pubkey, deinitialized_vote_account.clone()),
3222                (sysvar::clock::id(), clock_account),
3223                (authorized_withdrawer, AccountSharedData::default()),
3224            ],
3225            vec![
3226                AccountMeta {
3227                    pubkey: vote_pubkey,
3228                    is_signer: true,
3229                    is_writable: true,
3230                },
3231                AccountMeta {
3232                    pubkey: sysvar::clock::id(),
3233                    is_signer: false,
3234                    is_writable: false,
3235                },
3236            ],
3237            Err(InstructionError::InvalidAccountData),
3238        );
3239
3240        // Re-initialize with new fields.
3241        let new_node_pubkey = solana_pubkey::new_rand();
3242        let new_vote_init = VoteInit {
3243            node_pubkey: new_node_pubkey,
3244            authorized_voter: solana_pubkey::new_rand(),
3245            authorized_withdrawer: solana_pubkey::new_rand(),
3246            commission: 10,
3247        };
3248        // Fund the account for rent exemption.
3249        let mut funded_account = deinitialized_vote_account.clone();
3250        let rent = Rent::default();
3251        funded_account.set_lamports(rent.minimum_balance(funded_account.data().len()));
3252
3253        let accounts = process_instruction(
3254            features,
3255            &wincode::serialize(&VoteInstruction::InitializeAccount(new_vote_init)).unwrap(),
3256            vec![
3257                (vote_pubkey, funded_account),
3258                (sysvar::rent::id(), create_default_rent_account()),
3259                (sysvar::clock::id(), create_default_clock_account()),
3260                (new_node_pubkey, AccountSharedData::default()),
3261            ],
3262            vec![
3263                AccountMeta {
3264                    pubkey: vote_pubkey,
3265                    is_signer: false,
3266                    is_writable: true,
3267                },
3268                AccountMeta {
3269                    pubkey: sysvar::rent::id(),
3270                    is_signer: false,
3271                    is_writable: false,
3272                },
3273                AccountMeta {
3274                    pubkey: sysvar::clock::id(),
3275                    is_signer: false,
3276                    is_writable: false,
3277                },
3278                AccountMeta {
3279                    pubkey: new_node_pubkey,
3280                    is_signer: true,
3281                    is_writable: false,
3282                },
3283            ],
3284            Ok(()),
3285        );
3286
3287        // Verify the re-initialized account is a clean V4.
3288        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
3289        assert_eq!(*vote_state.node_pubkey(), new_node_pubkey);
3290        assert_eq!(
3291            *vote_state.authorized_withdrawer(),
3292            new_vote_init.authorized_withdrawer
3293        );
3294        assert_eq!(vote_state.commission(), 10);
3295        assert!(vote_state.votes().is_empty());
3296        assert!(vote_state.epoch_credits().is_empty());
3297    }
3298
3299    #[test]
3300    fn test_uninitialized_v3_blocked_under_v4() {
3301        // An uninitialized V3 account (empty authorized_voters, padded
3302        // to V4 size) is rejected by all instructions under V4.
3303        let vote_pubkey = solana_pubkey::new_rand();
3304
3305        // Create an uninitialized V3: discriminant 2, empty authorized_voters.
3306        let uninitialized_v3 = VoteStateVersions::V3(Box::default());
3307        let serialized = wincode::serialize(&uninitialized_v3).unwrap();
3308        let target_len = vote_state_size_of();
3309        let mut data = vec![0u8; target_len];
3310        data[..serialized.len()].copy_from_slice(&serialized);
3311
3312        let rent = Rent::default();
3313        let lamports = rent.minimum_balance(target_len);
3314        let vote_account = AccountSharedData::new_with_data(lamports, data, &id());
3315
3316        let authorized_withdrawer = solana_pubkey::new_rand();
3317        let features = VoteProgramFeatures::all_enabled();
3318
3319        // Authorize should fail.
3320        process_instruction(
3321            features,
3322            &wincode::serialize(&VoteInstruction::Authorize(
3323                solana_pubkey::new_rand(),
3324                VoteAuthorize::Voter,
3325            ))
3326            .unwrap(),
3327            vec![
3328                (vote_pubkey, vote_account.clone()),
3329                (sysvar::clock::id(), create_default_clock_account()),
3330                (authorized_withdrawer, AccountSharedData::default()),
3331            ],
3332            vec![
3333                AccountMeta {
3334                    pubkey: vote_pubkey,
3335                    is_signer: false,
3336                    is_writable: true,
3337                },
3338                AccountMeta {
3339                    pubkey: sysvar::clock::id(),
3340                    is_signer: false,
3341                    is_writable: false,
3342                },
3343                AccountMeta {
3344                    pubkey: authorized_withdrawer,
3345                    is_signer: true,
3346                    is_writable: false,
3347                },
3348            ],
3349            Err(InstructionError::UninitializedAccount),
3350        );
3351
3352        // UpdateCommission should fail.
3353        process_instruction(
3354            features,
3355            &wincode::serialize(&VoteInstruction::UpdateCommission(50)).unwrap(),
3356            vec![
3357                (vote_pubkey, vote_account.clone()),
3358                (authorized_withdrawer, AccountSharedData::default()),
3359                (sysvar::clock::id(), create_default_clock_account()),
3360                (
3361                    sysvar::epoch_schedule::id(),
3362                    create_sysvar_account(&solana_epoch_schedule::EpochSchedule::without_warmup()),
3363                ),
3364            ],
3365            vec![
3366                AccountMeta {
3367                    pubkey: vote_pubkey,
3368                    is_signer: false,
3369                    is_writable: true,
3370                },
3371                AccountMeta {
3372                    pubkey: authorized_withdrawer,
3373                    is_signer: true,
3374                    is_writable: false,
3375                },
3376            ],
3377            Err(InstructionError::UninitializedAccount),
3378        );
3379
3380        // Re-initialize escape hatch: InitializeAccount should succeed.
3381        let new_node = solana_pubkey::new_rand();
3382        let vote_init = VoteInit {
3383            node_pubkey: new_node,
3384            authorized_voter: solana_pubkey::new_rand(),
3385            authorized_withdrawer: solana_pubkey::new_rand(),
3386            commission: 5,
3387        };
3388        let accounts = process_instruction(
3389            features,
3390            &wincode::serialize(&VoteInstruction::InitializeAccount(vote_init)).unwrap(),
3391            vec![
3392                (vote_pubkey, vote_account.clone()),
3393                (sysvar::rent::id(), create_default_rent_account()),
3394                (sysvar::clock::id(), create_default_clock_account()),
3395                (new_node, AccountSharedData::default()),
3396            ],
3397            vec![
3398                AccountMeta {
3399                    pubkey: vote_pubkey,
3400                    is_signer: false,
3401                    is_writable: true,
3402                },
3403                AccountMeta {
3404                    pubkey: sysvar::rent::id(),
3405                    is_signer: false,
3406                    is_writable: false,
3407                },
3408                AccountMeta {
3409                    pubkey: sysvar::clock::id(),
3410                    is_signer: false,
3411                    is_writable: false,
3412                },
3413                AccountMeta {
3414                    pubkey: new_node,
3415                    is_signer: true,
3416                    is_writable: false,
3417                },
3418            ],
3419            Ok(()),
3420        );
3421
3422        // Verify re-initialized as V4.
3423        let versioned: VoteStateVersions = accounts[0].state().unwrap();
3424        assert!(matches!(versioned, VoteStateVersions::V4(_)));
3425        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
3426        assert_eq!(*vote_state.node_pubkey(), new_node);
3427        assert_eq!(vote_state.commission(), 5);
3428
3429        // InitializeAccountV2 should also work for the escape hatch.
3430        let new_node = solana_pubkey::new_rand();
3431        let (bls_pubkey, bls_proof_of_possession) =
3432            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
3433        let inflation_rewards_collector = solana_pubkey::new_rand();
3434        let block_revenue_collector = solana_pubkey::new_rand();
3435        let vote_init_v2 = VoteInitV2 {
3436            node_pubkey: new_node,
3437            authorized_voter: solana_pubkey::new_rand(),
3438            authorized_voter_bls_pubkey: bls_pubkey,
3439            authorized_voter_bls_proof_of_possession: bls_proof_of_possession,
3440            authorized_withdrawer: solana_pubkey::new_rand(),
3441            inflation_rewards_commission_bps: 1_234,
3442            block_revenue_commission_bps: 5_678,
3443        };
3444
3445        let collector_account = AccountSharedData::new(
3446            rent.minimum_balance(0),
3447            0,
3448            &solana_sdk_ids::system_program::id(),
3449        );
3450
3451        let accounts = process_instruction_with_cu_check(
3452            features,
3453            &wincode::serialize(&VoteInstruction::InitializeAccountV2(vote_init_v2)).unwrap(),
3454            vec![
3455                (vote_pubkey, vote_account),
3456                (new_node, AccountSharedData::default()),
3457                (inflation_rewards_collector, collector_account.clone()),
3458                (block_revenue_collector, collector_account),
3459                (sysvar::rent::id(), create_default_rent_account()),
3460                (sysvar::clock::id(), create_default_clock_account()),
3461            ],
3462            vec![
3463                AccountMeta {
3464                    pubkey: vote_pubkey,
3465                    is_signer: false,
3466                    is_writable: true,
3467                },
3468                AccountMeta {
3469                    pubkey: new_node,
3470                    is_signer: true,
3471                    is_writable: false,
3472                },
3473                AccountMeta {
3474                    pubkey: inflation_rewards_collector,
3475                    is_signer: false,
3476                    is_writable: true,
3477                },
3478                AccountMeta {
3479                    pubkey: block_revenue_collector,
3480                    is_signer: false,
3481                    is_writable: true,
3482                },
3483            ],
3484            Ok(()),
3485            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
3486        );
3487
3488        // Verify re-initialized as V4 with the v2-specific fields.
3489        let versioned: VoteStateVersions = accounts[0].state().unwrap();
3490        assert!(matches!(versioned, VoteStateVersions::V4(_)));
3491        let vote_state = deserialize_vote_state_for_test(accounts[0].data(), &vote_pubkey);
3492        let v4 = vote_state.as_ref_v4();
3493        assert_eq!(v4.node_pubkey, new_node);
3494        assert_eq!(v4.bls_pubkey_compressed, Some(bls_pubkey));
3495        assert_eq!(v4.inflation_rewards_commission_bps, 1_234);
3496        assert_eq!(v4.block_revenue_commission_bps, 5_678);
3497        assert_eq!(v4.inflation_rewards_collector, inflation_rewards_collector);
3498        assert_eq!(v4.block_revenue_collector, block_revenue_collector);
3499    }
3500
3501    fn perform_authorize_with_seed_test(
3502        bls_pubkey_management_in_vote_account: bool,
3503        authorization_type: VoteAuthorize,
3504        vote_pubkey: Pubkey,
3505        vote_account: AccountSharedData,
3506        current_authority_base_key: Pubkey,
3507        current_authority_seed: String,
3508        current_authority_owner: Pubkey,
3509        new_authority_pubkey: Pubkey,
3510    ) {
3511        let clock = Clock {
3512            epoch: 1,
3513            leader_schedule_epoch: 2,
3514            ..Clock::default()
3515        };
3516        let clock_account = create_sysvar_account(&clock);
3517        let transaction_accounts = vec![
3518            (vote_pubkey, vote_account),
3519            (sysvar::clock::id(), clock_account),
3520            (current_authority_base_key, AccountSharedData::default()),
3521        ];
3522        let mut instruction_accounts = vec![
3523            AccountMeta {
3524                pubkey: vote_pubkey,
3525                is_signer: false,
3526                is_writable: true,
3527            },
3528            AccountMeta {
3529                pubkey: sysvar::clock::id(),
3530                is_signer: false,
3531                is_writable: false,
3532            },
3533            AccountMeta {
3534                pubkey: current_authority_base_key,
3535                is_signer: true,
3536                is_writable: false,
3537            },
3538        ];
3539
3540        let features = VoteProgramFeatures {
3541            bls_pubkey_management_in_vote_account,
3542            ..Default::default()
3543        };
3544        let expected_cus = if matches!(authorization_type, VoteAuthorize::VoterWithBLS(_)) {
3545            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS
3546        } else {
3547            DEFAULT_COMPUTE_UNITS
3548        };
3549
3550        // Can't change authority unless base key signs.
3551        instruction_accounts[2].is_signer = false;
3552        process_instruction_with_cu_check(
3553            features,
3554            &wincode::serialize(&VoteInstruction::AuthorizeWithSeed(
3555                VoteAuthorizeWithSeedArgs {
3556                    authorization_type,
3557                    current_authority_derived_key_owner: current_authority_owner,
3558                    current_authority_derived_key_seed: current_authority_seed.clone(),
3559                    new_authority: new_authority_pubkey,
3560                },
3561            ))
3562            .unwrap(),
3563            transaction_accounts.clone(),
3564            instruction_accounts.clone(),
3565            Err(InstructionError::MissingRequiredSignature),
3566            expected_cus,
3567        );
3568        instruction_accounts[2].is_signer = true;
3569
3570        // Can't change authority if seed doesn't match.
3571        process_instruction_with_cu_check(
3572            features,
3573            &wincode::serialize(&VoteInstruction::AuthorizeWithSeed(
3574                VoteAuthorizeWithSeedArgs {
3575                    authorization_type,
3576                    current_authority_derived_key_owner: current_authority_owner,
3577                    current_authority_derived_key_seed: String::from("WRONG_SEED"),
3578                    new_authority: new_authority_pubkey,
3579                },
3580            ))
3581            .unwrap(),
3582            transaction_accounts.clone(),
3583            instruction_accounts.clone(),
3584            Err(InstructionError::MissingRequiredSignature),
3585            expected_cus,
3586        );
3587
3588        // Can't change authority if owner doesn't match.
3589        process_instruction_with_cu_check(
3590            features,
3591            &wincode::serialize(&VoteInstruction::AuthorizeWithSeed(
3592                VoteAuthorizeWithSeedArgs {
3593                    authorization_type,
3594                    current_authority_derived_key_owner: Pubkey::new_unique(), // Wrong owner.
3595                    current_authority_derived_key_seed: current_authority_seed.clone(),
3596                    new_authority: new_authority_pubkey,
3597                },
3598            ))
3599            .unwrap(),
3600            transaction_accounts.clone(),
3601            instruction_accounts.clone(),
3602            Err(InstructionError::MissingRequiredSignature),
3603            expected_cus,
3604        );
3605
3606        // Can change authority when base key signs for related derived key.
3607        process_instruction_with_cu_check(
3608            features,
3609            &wincode::serialize(&VoteInstruction::AuthorizeWithSeed(
3610                VoteAuthorizeWithSeedArgs {
3611                    authorization_type,
3612                    current_authority_derived_key_owner: current_authority_owner,
3613                    current_authority_derived_key_seed: current_authority_seed,
3614                    new_authority: new_authority_pubkey,
3615                },
3616            ))
3617            .unwrap(),
3618            transaction_accounts,
3619            instruction_accounts,
3620            Ok(()),
3621            expected_cus,
3622        );
3623    }
3624
3625    fn perform_authorize_checked_with_seed_test(
3626        bls_pubkey_management_in_vote_account: bool,
3627        authorization_type: VoteAuthorize,
3628        vote_pubkey: Pubkey,
3629        vote_account: AccountSharedData,
3630        current_authority_base_key: Pubkey,
3631        current_authority_seed: String,
3632        current_authority_owner: Pubkey,
3633        new_authority_pubkey: Pubkey,
3634    ) {
3635        let clock = Clock {
3636            epoch: 1,
3637            leader_schedule_epoch: 2,
3638            ..Clock::default()
3639        };
3640        let clock_account = create_sysvar_account(&clock);
3641        let transaction_accounts = vec![
3642            (vote_pubkey, vote_account),
3643            (sysvar::clock::id(), clock_account),
3644            (current_authority_base_key, AccountSharedData::default()),
3645            (new_authority_pubkey, AccountSharedData::default()),
3646        ];
3647        let mut instruction_accounts = vec![
3648            AccountMeta {
3649                pubkey: vote_pubkey,
3650                is_signer: false,
3651                is_writable: true,
3652            },
3653            AccountMeta {
3654                pubkey: sysvar::clock::id(),
3655                is_signer: false,
3656                is_writable: false,
3657            },
3658            AccountMeta {
3659                pubkey: current_authority_base_key,
3660                is_signer: true,
3661                is_writable: false,
3662            },
3663            AccountMeta {
3664                pubkey: new_authority_pubkey,
3665                is_signer: true,
3666                is_writable: false,
3667            },
3668        ];
3669
3670        let features = VoteProgramFeatures {
3671            bls_pubkey_management_in_vote_account,
3672            ..Default::default()
3673        };
3674        let expected_cus = if matches!(authorization_type, VoteAuthorize::VoterWithBLS(_)) {
3675            DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS
3676        } else {
3677            DEFAULT_COMPUTE_UNITS
3678        };
3679
3680        // Can't change authority unless base key signs.
3681        instruction_accounts[2].is_signer = false;
3682        process_instruction_with_cu_check(
3683            features,
3684            &wincode::serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
3685                VoteAuthorizeCheckedWithSeedArgs {
3686                    authorization_type,
3687                    current_authority_derived_key_owner: current_authority_owner,
3688                    current_authority_derived_key_seed: current_authority_seed.clone(),
3689                },
3690            ))
3691            .unwrap(),
3692            transaction_accounts.clone(),
3693            instruction_accounts.clone(),
3694            Err(InstructionError::MissingRequiredSignature),
3695            expected_cus,
3696        );
3697        instruction_accounts[2].is_signer = true;
3698
3699        // Can't change authority unless new authority signs.
3700        // This check happens before authorize(), so no BLS CUs are consumed.
3701        instruction_accounts[3].is_signer = false;
3702        process_instruction(
3703            features,
3704            &wincode::serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
3705                VoteAuthorizeCheckedWithSeedArgs {
3706                    authorization_type,
3707                    current_authority_derived_key_owner: current_authority_owner,
3708                    current_authority_derived_key_seed: current_authority_seed.clone(),
3709                },
3710            ))
3711            .unwrap(),
3712            transaction_accounts.clone(),
3713            instruction_accounts.clone(),
3714            Err(InstructionError::MissingRequiredSignature),
3715        );
3716        instruction_accounts[3].is_signer = true;
3717
3718        // Can't change authority if seed doesn't match.
3719        process_instruction_with_cu_check(
3720            features,
3721            &wincode::serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
3722                VoteAuthorizeCheckedWithSeedArgs {
3723                    authorization_type,
3724                    current_authority_derived_key_owner: current_authority_owner,
3725                    current_authority_derived_key_seed: String::from("WRONG_SEED"),
3726                },
3727            ))
3728            .unwrap(),
3729            transaction_accounts.clone(),
3730            instruction_accounts.clone(),
3731            Err(InstructionError::MissingRequiredSignature),
3732            expected_cus,
3733        );
3734
3735        // Can't change authority if owner doesn't match.
3736        process_instruction_with_cu_check(
3737            features,
3738            &wincode::serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
3739                VoteAuthorizeCheckedWithSeedArgs {
3740                    authorization_type,
3741                    current_authority_derived_key_owner: Pubkey::new_unique(), // Wrong owner.
3742                    current_authority_derived_key_seed: current_authority_seed.clone(),
3743                },
3744            ))
3745            .unwrap(),
3746            transaction_accounts.clone(),
3747            instruction_accounts.clone(),
3748            Err(InstructionError::MissingRequiredSignature),
3749            expected_cus,
3750        );
3751
3752        // Can change authority when base key signs for related derived key and new authority signs.
3753        process_instruction_with_cu_check(
3754            features,
3755            &wincode::serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
3756                VoteAuthorizeCheckedWithSeedArgs {
3757                    authorization_type,
3758                    current_authority_derived_key_owner: current_authority_owner,
3759                    current_authority_derived_key_seed: current_authority_seed,
3760                },
3761            ))
3762            .unwrap(),
3763            transaction_accounts,
3764            instruction_accounts,
3765            Ok(()),
3766            expected_cus,
3767        );
3768    }
3769
3770    #[test_matrix([false, true])]
3771    fn test_voter_base_key_can_authorize_new_voter(bls_pubkey_management_in_vote_account: bool) {
3772        let VoteAccountTestFixtureWithAuthorities {
3773            vote_pubkey,
3774            voter_base_key,
3775            voter_owner,
3776            voter_seed,
3777            vote_account,
3778            ..
3779        } = create_test_account_with_authorized_from_seed();
3780        let new_voter_pubkey = Pubkey::new_unique();
3781        let (bls_pubkey, bls_proof_of_possession) =
3782            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
3783        let authorize_type = if bls_pubkey_management_in_vote_account {
3784            VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
3785                bls_pubkey,
3786                bls_proof_of_possession,
3787            })
3788        } else {
3789            VoteAuthorize::Voter
3790        };
3791        perform_authorize_with_seed_test(
3792            bls_pubkey_management_in_vote_account,
3793            authorize_type,
3794            vote_pubkey,
3795            vote_account,
3796            voter_base_key,
3797            voter_seed,
3798            voter_owner,
3799            new_voter_pubkey,
3800        );
3801    }
3802
3803    #[test_matrix([false, true])]
3804    fn test_withdrawer_base_key_can_authorize_new_voter(
3805        bls_pubkey_management_in_vote_account: bool,
3806    ) {
3807        let VoteAccountTestFixtureWithAuthorities {
3808            vote_pubkey,
3809            withdrawer_base_key,
3810            withdrawer_owner,
3811            withdrawer_seed,
3812            vote_account,
3813            ..
3814        } = create_test_account_with_authorized_from_seed();
3815        let new_voter_pubkey = Pubkey::new_unique();
3816        let (bls_pubkey, bls_proof_of_possession) =
3817            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
3818        let authorize_type = if bls_pubkey_management_in_vote_account {
3819            VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
3820                bls_pubkey,
3821                bls_proof_of_possession,
3822            })
3823        } else {
3824            VoteAuthorize::Voter
3825        };
3826        perform_authorize_with_seed_test(
3827            bls_pubkey_management_in_vote_account,
3828            authorize_type,
3829            vote_pubkey,
3830            vote_account,
3831            withdrawer_base_key,
3832            withdrawer_seed,
3833            withdrawer_owner,
3834            new_voter_pubkey,
3835        );
3836    }
3837
3838    #[test]
3839    fn test_voter_base_key_can_not_authorize_new_withdrawer() {
3840        let VoteAccountTestFixtureWithAuthorities {
3841            vote_pubkey,
3842            voter_base_key,
3843            voter_owner,
3844            voter_seed,
3845            vote_account,
3846            ..
3847        } = create_test_account_with_authorized_from_seed();
3848        let new_withdrawer_pubkey = Pubkey::new_unique();
3849        let clock = Clock {
3850            epoch: 1,
3851            leader_schedule_epoch: 2,
3852            ..Clock::default()
3853        };
3854        let clock_account = create_sysvar_account(&clock);
3855        let transaction_accounts = vec![
3856            (vote_pubkey, vote_account),
3857            (sysvar::clock::id(), clock_account),
3858            (voter_base_key, AccountSharedData::default()),
3859        ];
3860        let instruction_accounts = vec![
3861            AccountMeta {
3862                pubkey: vote_pubkey,
3863                is_signer: false,
3864                is_writable: true,
3865            },
3866            AccountMeta {
3867                pubkey: sysvar::clock::id(),
3868                is_signer: false,
3869                is_writable: false,
3870            },
3871            AccountMeta {
3872                pubkey: voter_base_key,
3873                is_signer: true,
3874                is_writable: false,
3875            },
3876        ];
3877        // Despite having Voter authority, you may not change the Withdrawer authority.
3878        process_instruction(
3879            VoteProgramFeatures {
3880                ..Default::default()
3881            },
3882            &wincode::serialize(&VoteInstruction::AuthorizeWithSeed(
3883                VoteAuthorizeWithSeedArgs {
3884                    authorization_type: VoteAuthorize::Withdrawer,
3885                    current_authority_derived_key_owner: voter_owner,
3886                    current_authority_derived_key_seed: voter_seed,
3887                    new_authority: new_withdrawer_pubkey,
3888                },
3889            ))
3890            .unwrap(),
3891            transaction_accounts,
3892            instruction_accounts,
3893            Err(InstructionError::MissingRequiredSignature),
3894        );
3895    }
3896
3897    #[test_matrix([false, true])]
3898    fn test_withdrawer_base_key_can_authorize_new_withdrawer(
3899        bls_pubkey_management_in_vote_account: bool,
3900    ) {
3901        let VoteAccountTestFixtureWithAuthorities {
3902            vote_pubkey,
3903            withdrawer_base_key,
3904            withdrawer_owner,
3905            withdrawer_seed,
3906            vote_account,
3907            ..
3908        } = create_test_account_with_authorized_from_seed();
3909        let new_withdrawer_pubkey = Pubkey::new_unique();
3910        perform_authorize_with_seed_test(
3911            bls_pubkey_management_in_vote_account,
3912            VoteAuthorize::Withdrawer,
3913            vote_pubkey,
3914            vote_account,
3915            withdrawer_base_key,
3916            withdrawer_seed,
3917            withdrawer_owner,
3918            new_withdrawer_pubkey,
3919        );
3920    }
3921
3922    #[test_matrix([false, true])]
3923    fn test_voter_base_key_can_authorize_new_voter_checked(
3924        bls_pubkey_management_in_vote_account: bool,
3925    ) {
3926        let VoteAccountTestFixtureWithAuthorities {
3927            vote_pubkey,
3928            voter_base_key,
3929            voter_owner,
3930            voter_seed,
3931            vote_account,
3932            ..
3933        } = create_test_account_with_authorized_from_seed();
3934        let new_voter_pubkey = Pubkey::new_unique();
3935        let (bls_pubkey, bls_proof_of_possession) =
3936            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
3937        let authorize_type = if bls_pubkey_management_in_vote_account {
3938            VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
3939                bls_pubkey,
3940                bls_proof_of_possession,
3941            })
3942        } else {
3943            VoteAuthorize::Voter
3944        };
3945        perform_authorize_checked_with_seed_test(
3946            bls_pubkey_management_in_vote_account,
3947            authorize_type,
3948            vote_pubkey,
3949            vote_account,
3950            voter_base_key,
3951            voter_seed,
3952            voter_owner,
3953            new_voter_pubkey,
3954        );
3955    }
3956
3957    #[test_matrix([false, true])]
3958    fn test_withdrawer_base_key_can_authorize_new_voter_checked(
3959        bls_pubkey_management_in_vote_account: bool,
3960    ) {
3961        let VoteAccountTestFixtureWithAuthorities {
3962            vote_pubkey,
3963            withdrawer_base_key,
3964            withdrawer_owner,
3965            withdrawer_seed,
3966            vote_account,
3967            ..
3968        } = create_test_account_with_authorized_from_seed();
3969        let new_voter_pubkey = Pubkey::new_unique();
3970        let (bls_pubkey, bls_proof_of_possession) =
3971            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
3972        let authorize_type = if bls_pubkey_management_in_vote_account {
3973            VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
3974                bls_pubkey,
3975                bls_proof_of_possession,
3976            })
3977        } else {
3978            VoteAuthorize::Voter
3979        };
3980        perform_authorize_checked_with_seed_test(
3981            bls_pubkey_management_in_vote_account,
3982            authorize_type,
3983            vote_pubkey,
3984            vote_account,
3985            withdrawer_base_key,
3986            withdrawer_seed,
3987            withdrawer_owner,
3988            new_voter_pubkey,
3989        );
3990    }
3991
3992    #[test]
3993    fn test_voter_base_key_can_not_authorize_new_withdrawer_checked() {
3994        let VoteAccountTestFixtureWithAuthorities {
3995            vote_pubkey,
3996            voter_base_key,
3997            voter_owner,
3998            voter_seed,
3999            vote_account,
4000            ..
4001        } = create_test_account_with_authorized_from_seed();
4002        let new_withdrawer_pubkey = Pubkey::new_unique();
4003        let clock = Clock {
4004            epoch: 1,
4005            leader_schedule_epoch: 2,
4006            ..Clock::default()
4007        };
4008        let clock_account = create_sysvar_account(&clock);
4009        let transaction_accounts = vec![
4010            (vote_pubkey, vote_account),
4011            (sysvar::clock::id(), clock_account),
4012            (voter_base_key, AccountSharedData::default()),
4013            (new_withdrawer_pubkey, AccountSharedData::default()),
4014        ];
4015        let instruction_accounts = vec![
4016            AccountMeta {
4017                pubkey: vote_pubkey,
4018                is_signer: false,
4019                is_writable: true,
4020            },
4021            AccountMeta {
4022                pubkey: sysvar::clock::id(),
4023                is_signer: false,
4024                is_writable: false,
4025            },
4026            AccountMeta {
4027                pubkey: voter_base_key,
4028                is_signer: true,
4029                is_writable: false,
4030            },
4031            AccountMeta {
4032                pubkey: new_withdrawer_pubkey,
4033                is_signer: true,
4034                is_writable: false,
4035            },
4036        ];
4037        // Despite having Voter authority, you may not change the Withdrawer authority.
4038        process_instruction(
4039            VoteProgramFeatures {
4040                ..Default::default()
4041            },
4042            &wincode::serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
4043                VoteAuthorizeCheckedWithSeedArgs {
4044                    authorization_type: VoteAuthorize::Withdrawer,
4045                    current_authority_derived_key_owner: voter_owner,
4046                    current_authority_derived_key_seed: voter_seed,
4047                },
4048            ))
4049            .unwrap(),
4050            transaction_accounts,
4051            instruction_accounts,
4052            Err(InstructionError::MissingRequiredSignature),
4053        );
4054    }
4055
4056    #[test]
4057    fn test_withdrawer_base_key_can_authorize_new_withdrawer_checked() {
4058        let VoteAccountTestFixtureWithAuthorities {
4059            vote_pubkey,
4060            withdrawer_base_key,
4061            withdrawer_owner,
4062            withdrawer_seed,
4063            vote_account,
4064            ..
4065        } = create_test_account_with_authorized_from_seed();
4066        let new_withdrawer_pubkey = Pubkey::new_unique();
4067        perform_authorize_checked_with_seed_test(
4068            false,
4069            VoteAuthorize::Withdrawer,
4070            vote_pubkey,
4071            vote_account,
4072            withdrawer_base_key,
4073            withdrawer_seed,
4074            withdrawer_owner,
4075            new_withdrawer_pubkey,
4076        );
4077    }
4078
4079    #[test]
4080    fn test_spoofed_vote() {
4081        let features = VoteProgramFeatures {
4082            ..Default::default()
4083        };
4084        process_instruction_as_one_arg(
4085            features,
4086            &vote(
4087                &invalid_vote_state_pubkey(),
4088                &Pubkey::new_unique(),
4089                Vote::default(),
4090            ),
4091            Err(InstructionError::InvalidAccountOwner),
4092        );
4093        process_instruction_as_one_arg(
4094            features,
4095            &update_vote_state(
4096                &invalid_vote_state_pubkey(),
4097                &Pubkey::default(),
4098                VoteStateUpdate::default(),
4099            ),
4100            Err(InstructionError::InvalidAccountOwner),
4101        );
4102        process_instruction_as_one_arg(
4103            features,
4104            &compact_update_vote_state(
4105                &invalid_vote_state_pubkey(),
4106                &Pubkey::default(),
4107                VoteStateUpdate::default(),
4108            ),
4109            Err(InstructionError::InvalidAccountOwner),
4110        );
4111        process_instruction_as_one_arg(
4112            features,
4113            &tower_sync(
4114                &invalid_vote_state_pubkey(),
4115                &Pubkey::default(),
4116                TowerSync::default(),
4117            ),
4118            Err(InstructionError::InvalidAccountOwner),
4119        );
4120    }
4121
4122    #[test]
4123    fn test_create_account_vote_state_1_14_11() {
4124        let node_pubkey = Pubkey::new_unique();
4125        let vote_pubkey = Pubkey::new_unique();
4126        let instructions = create_account_with_config(
4127            &node_pubkey,
4128            &vote_pubkey,
4129            &VoteInit {
4130                node_pubkey,
4131                authorized_voter: vote_pubkey,
4132                authorized_withdrawer: vote_pubkey,
4133                commission: 0,
4134            },
4135            101,
4136            CreateVoteAccountConfig {
4137                space: vote_state::VoteState1_14_11::size_of() as u64,
4138                ..CreateVoteAccountConfig::default()
4139            },
4140        );
4141        // grab the `space` value from SystemInstruction::CreateAccount by directly indexing, for
4142        // expediency
4143        let space = usize::from_le_bytes(instructions[0].data[12..20].try_into().unwrap());
4144        assert_eq!(space, vote_state::VoteState1_14_11::size_of());
4145        let empty_vote_account = AccountSharedData::new(101, space, &id());
4146
4147        let transaction_accounts = vec![
4148            (vote_pubkey, empty_vote_account),
4149            (node_pubkey, AccountSharedData::default()),
4150            (sysvar::clock::id(), create_default_clock_account()),
4151            (sysvar::rent::id(), create_default_rent_account()),
4152        ];
4153
4154        // should fail, since VoteState1_14_11 isn't supported anymore
4155        process_instruction(
4156            VoteProgramFeatures {
4157                ..Default::default()
4158            },
4159            &instructions[1].data,
4160            transaction_accounts,
4161            instructions[1].accounts.clone(),
4162            Err(InstructionError::InvalidAccountData),
4163        );
4164    }
4165
4166    #[test]
4167    fn test_create_account_vote_state_current() {
4168        let node_pubkey = Pubkey::new_unique();
4169        let vote_pubkey = Pubkey::new_unique();
4170        let instructions = create_account_with_config(
4171            &node_pubkey,
4172            &vote_pubkey,
4173            &VoteInit {
4174                node_pubkey,
4175                authorized_voter: vote_pubkey,
4176                authorized_withdrawer: vote_pubkey,
4177                commission: 0,
4178            },
4179            101,
4180            CreateVoteAccountConfig {
4181                space: vote_state_size_of() as u64,
4182                ..CreateVoteAccountConfig::default()
4183            },
4184        );
4185        // grab the `space` value from SystemInstruction::CreateAccount by directly indexing, for
4186        // expediency
4187        let space = usize::from_le_bytes(instructions[0].data[12..20].try_into().unwrap());
4188        assert_eq!(space, vote_state_size_of());
4189        let empty_vote_account = AccountSharedData::new(101, space, &id());
4190
4191        let transaction_accounts = vec![
4192            (vote_pubkey, empty_vote_account),
4193            (node_pubkey, AccountSharedData::default()),
4194            (sysvar::clock::id(), create_default_clock_account()),
4195            (sysvar::rent::id(), create_default_rent_account()),
4196        ];
4197
4198        process_instruction(
4199            VoteProgramFeatures {
4200                ..Default::default()
4201            },
4202            &instructions[1].data,
4203            transaction_accounts,
4204            instructions[1].accounts.clone(),
4205            Ok(()),
4206        );
4207    }
4208
4209    #[test]
4210    fn test_vote_process_instruction() {
4211        agave_logger::setup();
4212        let instructions = create_account_with_config(
4213            &Pubkey::new_unique(),
4214            &Pubkey::new_unique(),
4215            &VoteInit::default(),
4216            101,
4217            CreateVoteAccountConfig::default(),
4218        );
4219        let features = VoteProgramFeatures {
4220            ..Default::default()
4221        };
4222        // this case fails regardless of CreateVoteAccountConfig::space, because
4223        // process_instruction_as_one_arg passes a default (empty) account
4224        process_instruction_as_one_arg(
4225            features,
4226            &instructions[1],
4227            Err(InstructionError::InvalidAccountData),
4228        );
4229        process_instruction_as_one_arg(
4230            features,
4231            &vote(
4232                &Pubkey::new_unique(),
4233                &Pubkey::new_unique(),
4234                Vote::default(),
4235            ),
4236            Err(InstructionError::InvalidInstructionData),
4237        );
4238        process_instruction_as_one_arg(
4239            features,
4240            &vote_switch(
4241                &Pubkey::new_unique(),
4242                &Pubkey::new_unique(),
4243                Vote::default(),
4244                Hash::default(),
4245            ),
4246            Err(InstructionError::InvalidInstructionData),
4247        );
4248        process_instruction_as_one_arg(
4249            features,
4250            &authorize(
4251                &Pubkey::new_unique(),
4252                &Pubkey::new_unique(),
4253                &Pubkey::new_unique(),
4254                VoteAuthorize::Voter,
4255            ),
4256            Err(InstructionError::InvalidAccountData),
4257        );
4258        process_instruction_as_one_arg(
4259            features,
4260            &update_vote_state(
4261                &Pubkey::default(),
4262                &Pubkey::default(),
4263                VoteStateUpdate::default(),
4264            ),
4265            Err(InstructionError::InvalidInstructionData),
4266        );
4267
4268        process_instruction_as_one_arg(
4269            features,
4270            &update_vote_state_switch(
4271                &Pubkey::default(),
4272                &Pubkey::default(),
4273                VoteStateUpdate::default(),
4274                Hash::default(),
4275            ),
4276            Err(InstructionError::InvalidInstructionData),
4277        );
4278        process_instruction_as_one_arg(
4279            features,
4280            &compact_update_vote_state(
4281                &Pubkey::default(),
4282                &Pubkey::default(),
4283                VoteStateUpdate::default(),
4284            ),
4285            Err(InstructionError::InvalidInstructionData),
4286        );
4287        process_instruction_as_one_arg(
4288            features,
4289            &compact_update_vote_state_switch(
4290                &Pubkey::default(),
4291                &Pubkey::default(),
4292                VoteStateUpdate::default(),
4293                Hash::default(),
4294            ),
4295            Err(InstructionError::InvalidInstructionData),
4296        );
4297        process_instruction_as_one_arg(
4298            features,
4299            &tower_sync(&Pubkey::default(), &Pubkey::default(), TowerSync::default()),
4300            Err(InstructionError::InvalidAccountData),
4301        );
4302        process_instruction_as_one_arg(
4303            features,
4304            &tower_sync_switch(
4305                &Pubkey::default(),
4306                &Pubkey::default(),
4307                TowerSync::default(),
4308                Hash::default(),
4309            ),
4310            Err(InstructionError::InvalidAccountData),
4311        );
4312
4313        process_instruction_as_one_arg(
4314            features,
4315            &update_validator_identity(
4316                &Pubkey::new_unique(),
4317                &Pubkey::new_unique(),
4318                &Pubkey::new_unique(),
4319            ),
4320            Err(InstructionError::InvalidAccountData),
4321        );
4322        process_instruction_as_one_arg(
4323            features,
4324            &update_commission(&Pubkey::new_unique(), &Pubkey::new_unique(), 0),
4325            Err(InstructionError::InvalidAccountData),
4326        );
4327
4328        process_instruction_as_one_arg(
4329            features,
4330            &withdraw(
4331                &Pubkey::new_unique(),
4332                &Pubkey::new_unique(),
4333                0,
4334                &Pubkey::new_unique(),
4335            ),
4336            Err(InstructionError::InvalidAccountData),
4337        );
4338    }
4339
4340    #[test]
4341    fn test_tower_sync_rejected_after_alpenglow_migration_succeeds() {
4342        let features = VoteProgramFeatures {
4343            alpenglow_migration_succeeded: true,
4344            ..Default::default()
4345        };
4346
4347        process_instruction_as_one_arg(
4348            features,
4349            &tower_sync(&Pubkey::default(), &Pubkey::default(), TowerSync::default()),
4350            Err(InstructionError::InvalidInstructionData),
4351        );
4352        process_instruction_as_one_arg(
4353            features,
4354            &tower_sync_switch(
4355                &Pubkey::default(),
4356                &Pubkey::default(),
4357                TowerSync::default(),
4358                Hash::default(),
4359            ),
4360            Err(InstructionError::InvalidInstructionData),
4361        );
4362    }
4363
4364    #[test_matrix([false, true])]
4365    fn test_vote_authorize_checked(bls_pubkey_management_in_vote_account: bool) {
4366        let vote_pubkey = Pubkey::new_unique();
4367        let authorized_pubkey = Pubkey::new_unique();
4368        let new_authorized_pubkey = Pubkey::new_unique();
4369
4370        let features = VoteProgramFeatures {
4371            bls_pubkey_management_in_vote_account,
4372            ..Default::default()
4373        };
4374
4375        // Test with vanilla authorize accounts
4376        let (bls_pubkey, bls_proof_of_possession) =
4377            create_bls_pubkey_and_proof_of_possession(&vote_pubkey);
4378        let mut instruction = if bls_pubkey_management_in_vote_account {
4379            authorize_checked(
4380                &vote_pubkey,
4381                &authorized_pubkey,
4382                &new_authorized_pubkey,
4383                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4384                    bls_pubkey,
4385                    bls_proof_of_possession,
4386                }),
4387            )
4388        } else {
4389            authorize_checked(
4390                &vote_pubkey,
4391                &authorized_pubkey,
4392                &new_authorized_pubkey,
4393                VoteAuthorize::Voter,
4394            )
4395        };
4396        instruction.accounts = instruction.accounts[0..2].to_vec();
4397        process_instruction_as_one_arg(
4398            features,
4399            &instruction,
4400            Err(InstructionError::MissingAccount),
4401        );
4402
4403        let mut instruction = authorize_checked(
4404            &vote_pubkey,
4405            &authorized_pubkey,
4406            &new_authorized_pubkey,
4407            VoteAuthorize::Withdrawer,
4408        );
4409        instruction.accounts = instruction.accounts[0..2].to_vec();
4410        process_instruction_as_one_arg(
4411            features,
4412            &instruction,
4413            Err(InstructionError::MissingAccount),
4414        );
4415
4416        // Test with non-signing new_authorized_pubkey
4417        let mut instruction = if bls_pubkey_management_in_vote_account {
4418            authorize_checked(
4419                &vote_pubkey,
4420                &authorized_pubkey,
4421                &new_authorized_pubkey,
4422                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4423                    bls_pubkey,
4424                    bls_proof_of_possession,
4425                }),
4426            )
4427        } else {
4428            authorize_checked(
4429                &vote_pubkey,
4430                &authorized_pubkey,
4431                &new_authorized_pubkey,
4432                VoteAuthorize::Voter,
4433            )
4434        };
4435        instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false);
4436        process_instruction_as_one_arg(
4437            features,
4438            &instruction,
4439            Err(InstructionError::MissingRequiredSignature),
4440        );
4441
4442        let mut instruction = authorize_checked(
4443            &vote_pubkey,
4444            &authorized_pubkey,
4445            &new_authorized_pubkey,
4446            VoteAuthorize::Withdrawer,
4447        );
4448        instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false);
4449        process_instruction_as_one_arg(
4450            features,
4451            &instruction,
4452            Err(InstructionError::MissingRequiredSignature),
4453        );
4454
4455        // Test with new_authorized_pubkey signer
4456        let default_authorized_pubkey = Pubkey::default();
4457        let vote_account = create_test_account_with_provided_authorized(
4458            &default_authorized_pubkey,
4459            &default_authorized_pubkey,
4460        );
4461        let clock_address = sysvar::clock::id();
4462        let clock_account = create_sysvar_account(&Clock::default());
4463        let authorized_account = create_default_account();
4464        let new_authorized_account = create_default_account();
4465        let transaction_accounts = vec![
4466            (vote_pubkey, vote_account),
4467            (clock_address, clock_account),
4468            (default_authorized_pubkey, authorized_account),
4469            (new_authorized_pubkey, new_authorized_account),
4470        ];
4471        let instruction_accounts = vec![
4472            AccountMeta {
4473                pubkey: vote_pubkey,
4474                is_signer: false,
4475                is_writable: true,
4476            },
4477            AccountMeta {
4478                pubkey: clock_address,
4479                is_signer: false,
4480                is_writable: false,
4481            },
4482            AccountMeta {
4483                pubkey: default_authorized_pubkey,
4484                is_signer: true,
4485                is_writable: false,
4486            },
4487            AccountMeta {
4488                pubkey: new_authorized_pubkey,
4489                is_signer: true,
4490                is_writable: false,
4491            },
4492        ];
4493        let (authorize_type, expected_cus) = if bls_pubkey_management_in_vote_account {
4494            (
4495                VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
4496                    bls_pubkey,
4497                    bls_proof_of_possession,
4498                }),
4499                DEFAULT_COMPUTE_UNITS + BLS_PROOF_OF_POSSESSION_VERIFICATION_COMPUTE_UNITS,
4500            )
4501        } else {
4502            (VoteAuthorize::Voter, DEFAULT_COMPUTE_UNITS)
4503        };
4504        process_instruction_with_cu_check(
4505            features,
4506            &wincode::serialize(&VoteInstruction::AuthorizeChecked(authorize_type)).unwrap(),
4507            transaction_accounts.clone(),
4508            instruction_accounts.clone(),
4509            Ok(()),
4510            expected_cus,
4511        );
4512        process_instruction(
4513            features,
4514            &wincode::serialize(&VoteInstruction::AuthorizeChecked(
4515                VoteAuthorize::Withdrawer,
4516            ))
4517            .unwrap(),
4518            transaction_accounts,
4519            instruction_accounts,
4520            Ok(()),
4521        );
4522    }
4523
4524    // Explicitly covers uninitialized vote accounts for instructions.
4525    // `test_vote_signature` above covered:
4526    // * Vote
4527    // * UpdateVoteState
4528    // * CompactUpdateVoteState
4529    // * TowerSync
4530    #[test]
4531    fn test_uninitialized_vote_account() {
4532        // Set up uninitialized vote account.
4533        let vote_pubkey = solana_pubkey::new_rand();
4534        let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
4535
4536        let expected_error = InstructionError::InvalidAccountData;
4537
4538        let features = VoteProgramFeatures {
4539            ..Default::default()
4540        };
4541
4542        // VoteInstruction::Authorize
4543        {
4544            let new_authorized_pubkey = solana_pubkey::new_rand();
4545
4546            let instruction_data = wincode::serialize(&VoteInstruction::Authorize(
4547                new_authorized_pubkey,
4548                VoteAuthorize::Voter,
4549            ))
4550            .unwrap();
4551
4552            let transaction_accounts = vec![
4553                (vote_pubkey, vote_account),
4554                (sysvar::clock::id(), create_default_clock_account()),
4555            ];
4556
4557            let instruction_accounts = vec![
4558                AccountMeta {
4559                    pubkey: vote_pubkey,
4560                    is_signer: true,
4561                    is_writable: true,
4562                },
4563                AccountMeta {
4564                    pubkey: sysvar::clock::id(),
4565                    is_signer: false,
4566                    is_writable: false,
4567                },
4568            ];
4569
4570            process_instruction(
4571                features,
4572                &instruction_data,
4573                transaction_accounts,
4574                instruction_accounts,
4575                Err(expected_error.clone()),
4576            );
4577        }
4578
4579        // VoteInstruction::AuthorizeWithSeed
4580        {
4581            let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
4582            let current_authority_base_key = Pubkey::new_unique();
4583            let current_authority_owner = Pubkey::new_unique();
4584            let new_authority_pubkey = Pubkey::new_unique();
4585
4586            let instruction_data = wincode::serialize(&VoteInstruction::AuthorizeWithSeed(
4587                VoteAuthorizeWithSeedArgs {
4588                    authorization_type: VoteAuthorize::Voter,
4589                    current_authority_derived_key_owner: current_authority_owner,
4590                    current_authority_derived_key_seed: String::from("SEED"),
4591                    new_authority: new_authority_pubkey,
4592                },
4593            ))
4594            .unwrap();
4595
4596            let transaction_accounts = vec![
4597                (vote_pubkey, vote_account),
4598                (sysvar::clock::id(), create_default_clock_account()),
4599                (current_authority_base_key, AccountSharedData::default()),
4600            ];
4601
4602            let instruction_accounts = vec![
4603                AccountMeta {
4604                    pubkey: vote_pubkey,
4605                    is_signer: false,
4606                    is_writable: true,
4607                },
4608                AccountMeta {
4609                    pubkey: sysvar::clock::id(),
4610                    is_signer: false,
4611                    is_writable: false,
4612                },
4613                AccountMeta {
4614                    pubkey: current_authority_base_key,
4615                    is_signer: true,
4616                    is_writable: false,
4617                },
4618            ];
4619
4620            process_instruction(
4621                features,
4622                &instruction_data,
4623                transaction_accounts,
4624                instruction_accounts,
4625                Err(expected_error.clone()),
4626            );
4627        }
4628
4629        // VoteInstruction::AuthorizeCheckedWithSeed
4630        {
4631            let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
4632            let current_authority_base_key = Pubkey::new_unique();
4633            let current_authority_owner = Pubkey::new_unique();
4634            let new_authority_pubkey = Pubkey::new_unique();
4635
4636            let instruction_data = wincode::serialize(&VoteInstruction::AuthorizeCheckedWithSeed(
4637                VoteAuthorizeCheckedWithSeedArgs {
4638                    authorization_type: VoteAuthorize::Voter,
4639                    current_authority_derived_key_owner: current_authority_owner,
4640                    current_authority_derived_key_seed: String::from("SEED"),
4641                },
4642            ))
4643            .unwrap();
4644
4645            let transaction_accounts = vec![
4646                (vote_pubkey, vote_account),
4647                (sysvar::clock::id(), create_default_clock_account()),
4648                (current_authority_base_key, AccountSharedData::default()),
4649                (new_authority_pubkey, AccountSharedData::default()),
4650            ];
4651
4652            let instruction_accounts = vec![
4653                AccountMeta {
4654                    pubkey: vote_pubkey,
4655                    is_signer: false,
4656                    is_writable: true,
4657                },
4658                AccountMeta {
4659                    pubkey: sysvar::clock::id(),
4660                    is_signer: false,
4661                    is_writable: false,
4662                },
4663                AccountMeta {
4664                    pubkey: current_authority_base_key,
4665                    is_signer: true,
4666                    is_writable: false,
4667                },
4668                AccountMeta {
4669                    pubkey: new_authority_pubkey,
4670                    is_signer: true,
4671                    is_writable: false,
4672                },
4673            ];
4674
4675            process_instruction(
4676                features,
4677                &instruction_data,
4678                transaction_accounts,
4679                instruction_accounts,
4680                Err(expected_error.clone()),
4681            );
4682        }
4683
4684        // VoteInstruction::UpdateValidatorIdentity
4685        {
4686            let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
4687            let node_pubkey = Pubkey::new_unique();
4688            let authorized_withdrawer = Pubkey::new_unique();
4689
4690            let instruction_data =
4691                wincode::serialize(&VoteInstruction::UpdateValidatorIdentity).unwrap();
4692
4693            let transaction_accounts = vec![
4694                (vote_pubkey, vote_account),
4695                (node_pubkey, AccountSharedData::default()),
4696                (authorized_withdrawer, AccountSharedData::default()),
4697            ];
4698
4699            let instruction_accounts = vec![
4700                AccountMeta {
4701                    pubkey: vote_pubkey,
4702                    is_signer: false,
4703                    is_writable: true,
4704                },
4705                AccountMeta {
4706                    pubkey: node_pubkey,
4707                    is_signer: true,
4708                    is_writable: false,
4709                },
4710                AccountMeta {
4711                    pubkey: authorized_withdrawer,
4712                    is_signer: true,
4713                    is_writable: false,
4714                },
4715            ];
4716
4717            process_instruction(
4718                features,
4719                &instruction_data,
4720                transaction_accounts,
4721                instruction_accounts,
4722                Err(expected_error.clone()),
4723            );
4724        }
4725
4726        // VoteInstruction::UpdateCommission
4727        {
4728            let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
4729            let authorized_withdrawer = Pubkey::new_unique();
4730
4731            let instruction_data =
4732                wincode::serialize(&VoteInstruction::UpdateCommission(42)).unwrap();
4733
4734            let transaction_accounts = vec![
4735                (vote_pubkey, vote_account),
4736                (authorized_withdrawer, AccountSharedData::default()),
4737                (
4738                    sysvar::clock::id(),
4739                    create_sysvar_account(&Clock::default()),
4740                ),
4741                (
4742                    sysvar::epoch_schedule::id(),
4743                    create_sysvar_account(&EpochSchedule::without_warmup()),
4744                ),
4745            ];
4746
4747            let instruction_accounts = vec![
4748                AccountMeta {
4749                    pubkey: vote_pubkey,
4750                    is_signer: false,
4751                    is_writable: true,
4752                },
4753                AccountMeta {
4754                    pubkey: authorized_withdrawer,
4755                    is_signer: true,
4756                    is_writable: false,
4757                },
4758            ];
4759
4760            process_instruction(
4761                features,
4762                &instruction_data,
4763                transaction_accounts,
4764                instruction_accounts,
4765                Err(expected_error.clone()),
4766            );
4767        }
4768
4769        // VoteInstruction::Withdraw
4770        {
4771            let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
4772            let recipient = Pubkey::new_unique();
4773
4774            let instruction_data = wincode::serialize(&VoteInstruction::Withdraw(10)).unwrap();
4775
4776            let transaction_accounts = vec![
4777                (vote_pubkey, vote_account),
4778                (recipient, AccountSharedData::default()),
4779                (sysvar::clock::id(), create_default_clock_account()),
4780                (sysvar::rent::id(), create_default_rent_account()),
4781            ];
4782
4783            let instruction_accounts = vec![
4784                AccountMeta {
4785                    pubkey: vote_pubkey,
4786                    is_signer: true,
4787                    is_writable: true,
4788                },
4789                AccountMeta {
4790                    pubkey: recipient,
4791                    is_signer: false,
4792                    is_writable: true,
4793                },
4794            ];
4795
4796            process_instruction(
4797                features,
4798                &instruction_data,
4799                transaction_accounts,
4800                instruction_accounts,
4801                Err(expected_error.clone()),
4802            );
4803        }
4804
4805        // VoteInstruction::AuthorizeChecked
4806        {
4807            let vote_account = AccountSharedData::new(100, vote_state_size_of(), &id());
4808            let authorized_pubkey = Pubkey::new_unique();
4809            let new_authorized_pubkey = Pubkey::new_unique();
4810
4811            let instruction_data =
4812                wincode::serialize(&VoteInstruction::AuthorizeChecked(VoteAuthorize::Voter))
4813                    .unwrap();
4814
4815            let transaction_accounts = vec![
4816                (vote_pubkey, vote_account),
4817                (sysvar::clock::id(), create_default_clock_account()),
4818                (authorized_pubkey, AccountSharedData::default()),
4819                (new_authorized_pubkey, AccountSharedData::default()),
4820            ];
4821
4822            let instruction_accounts = vec![
4823                AccountMeta {
4824                    pubkey: vote_pubkey,
4825                    is_signer: false,
4826                    is_writable: true,
4827                },
4828                AccountMeta {
4829                    pubkey: sysvar::clock::id(),
4830                    is_signer: false,
4831                    is_writable: false,
4832                },
4833                AccountMeta {
4834                    pubkey: authorized_pubkey,
4835                    is_signer: true,
4836                    is_writable: false,
4837                },
4838                AccountMeta {
4839                    pubkey: new_authorized_pubkey,
4840                    is_signer: true,
4841                    is_writable: false,
4842                },
4843            ];
4844
4845            process_instruction(
4846                features,
4847                &instruction_data,
4848                transaction_accounts,
4849                instruction_accounts,
4850                Err(expected_error),
4851            );
4852        }
4853    }
4854
4855    #[test]
4856    fn test_deposit_delegator_rewards_fails() {
4857        let (vote_pubkey, _authorized_voter, _authorized_withdrawer, vote_account_v4) =
4858            create_test_account_with_authorized();
4859
4860        // Create source account with enough lamports to transfer.
4861        let source_pubkey = Pubkey::new_unique();
4862        let source_lamports = 1_000_000;
4863        let source_account =
4864            AccountSharedData::new(source_lamports, 0, &solana_sdk_ids::system_program::id());
4865
4866        let deposit_amount = 100_000;
4867
4868        let instruction_data = wincode::serialize(&VoteInstruction::DepositDelegatorRewards {
4869            deposit: deposit_amount,
4870        })
4871        .unwrap();
4872
4873        let instruction_accounts = vec![
4874            AccountMeta {
4875                pubkey: vote_pubkey,
4876                is_signer: false,
4877                is_writable: true,
4878            },
4879            AccountMeta {
4880                pubkey: source_pubkey,
4881                is_signer: true,
4882                is_writable: true,
4883            },
4884            AccountMeta {
4885                pubkey: solana_sdk_ids::system_program::id(),
4886                is_signer: false,
4887                is_writable: false,
4888            },
4889        ];
4890
4891        let transaction_accounts = vec![
4892            (vote_pubkey, vote_account_v4.clone()),
4893            (source_pubkey, source_account.clone()),
4894            (
4895                solana_sdk_ids::system_program::id(),
4896                AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id()),
4897            ),
4898        ];
4899
4900        process_instruction(
4901            VoteProgramFeatures::all_enabled(),
4902            &instruction_data,
4903            transaction_accounts.clone(),
4904            instruction_accounts.clone(),
4905            Err(InstructionError::InvalidInstructionData),
4906        );
4907    }
4908
4909    // Test DepositDelegatorRewards instruction (SIMD-0123).
4910    #[ignore]
4911    #[test]
4912    fn test_deposit_delegator_rewards() {
4913        const DEPOSIT_DELEGATOR_REWARDS_COMPUTE_UNITS: u64 =
4914            DEFAULT_COMPUTE_UNITS + SYSTEM_PROGRAM_COMPUTE_UNITS;
4915
4916        let (vote_pubkey, _authorized_voter, _authorized_withdrawer, vote_account_v4) =
4917            create_test_account_with_authorized();
4918        let (vote_pubkey_v3, vote_account_v3) = create_test_account_v3();
4919
4920        // Create source account with enough lamports to transfer.
4921        let source_pubkey = Pubkey::new_unique();
4922        let source_lamports = 1_000_000;
4923        let source_account =
4924            AccountSharedData::new(source_lamports, 0, &solana_sdk_ids::system_program::id());
4925
4926        let deposit_amount = 100_000;
4927
4928        let instruction_data = wincode::serialize(&VoteInstruction::DepositDelegatorRewards {
4929            deposit: deposit_amount,
4930        })
4931        .unwrap();
4932
4933        let instruction_accounts = vec![
4934            AccountMeta {
4935                pubkey: vote_pubkey,
4936                is_signer: false,
4937                is_writable: true,
4938            },
4939            AccountMeta {
4940                pubkey: source_pubkey,
4941                is_signer: true,
4942                is_writable: true,
4943            },
4944            AccountMeta {
4945                pubkey: solana_sdk_ids::system_program::id(),
4946                is_signer: false,
4947                is_writable: false,
4948            },
4949        ];
4950
4951        let transaction_accounts = vec![
4952            (vote_pubkey, vote_account_v4.clone()),
4953            (source_pubkey, source_account.clone()),
4954            (
4955                solana_sdk_ids::system_program::id(),
4956                AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id()),
4957            ),
4958        ];
4959
4960        // Fail - SIMD-0291: commission_rate_in_basis_points disabled.
4961        process_instruction(
4962            VoteProgramFeatures {
4963                commission_rate_in_basis_points: false,
4964                custom_commission_collector: true,
4965                block_revenue_sharing: true,
4966                ..Default::default()
4967            },
4968            &instruction_data,
4969            transaction_accounts.clone(),
4970            instruction_accounts.clone(),
4971            Err(InstructionError::InvalidInstructionData),
4972        );
4973
4974        // Fail - SIMD-0232: custom_commission_collector disabled.
4975        process_instruction(
4976            VoteProgramFeatures {
4977                commission_rate_in_basis_points: true,
4978                custom_commission_collector: false,
4979                block_revenue_sharing: true,
4980                ..Default::default()
4981            },
4982            &instruction_data,
4983            transaction_accounts.clone(),
4984            instruction_accounts.clone(),
4985            Err(InstructionError::InvalidInstructionData),
4986        );
4987
4988        // Fail - SIMD-0123: block_revenue_sharing disabled.
4989        process_instruction(
4990            VoteProgramFeatures {
4991                commission_rate_in_basis_points: true,
4992                custom_commission_collector: true,
4993                block_revenue_sharing: false,
4994                ..Default::default()
4995            },
4996            &instruction_data,
4997            transaction_accounts.clone(),
4998            instruction_accounts.clone(),
4999            Err(InstructionError::InvalidInstructionData),
5000        );
5001
5002        // Fail - Not enough accounts (less than 2).
5003        let single_account_instruction_accounts = vec![AccountMeta {
5004            pubkey: vote_pubkey,
5005            is_signer: false,
5006            is_writable: true,
5007        }];
5008        process_instruction(
5009            VoteProgramFeatures::all_enabled(),
5010            &instruction_data,
5011            transaction_accounts.clone(),
5012            single_account_instruction_accounts,
5013            Err(InstructionError::MissingAccount),
5014        );
5015
5016        // Fail - Source account is not a signer.
5017        let non_signer_instruction_accounts = vec![
5018            AccountMeta {
5019                pubkey: vote_pubkey,
5020                is_signer: false,
5021                is_writable: true,
5022            },
5023            AccountMeta {
5024                pubkey: source_pubkey,
5025                is_signer: false,
5026                is_writable: true,
5027            },
5028            AccountMeta {
5029                pubkey: solana_sdk_ids::system_program::id(),
5030                is_signer: false,
5031                is_writable: false,
5032            },
5033        ];
5034        process_instruction(
5035            VoteProgramFeatures::all_enabled(),
5036            &instruction_data,
5037            transaction_accounts.clone(),
5038            non_signer_instruction_accounts,
5039            Err(InstructionError::MissingRequiredSignature),
5040        );
5041
5042        // Fail - Vote account fails to deserialize (zeroed/uninitialized data).
5043        let invalid_vote_account = AccountSharedData::new(1_000_000, VoteStateV4::size_of(), &id());
5044        process_instruction(
5045            VoteProgramFeatures::all_enabled(),
5046            &instruction_data,
5047            vec![
5048                (vote_pubkey, invalid_vote_account),
5049                (source_pubkey, source_account.clone()),
5050            ],
5051            instruction_accounts.clone(),
5052            Err(InstructionError::InvalidAccountData),
5053        );
5054
5055        // Fail - Vote account is initialized but V3 (not V4).
5056        let instruction_accounts_v3 = vec![
5057            AccountMeta {
5058                pubkey: vote_pubkey_v3,
5059                is_signer: false,
5060                is_writable: true,
5061            },
5062            AccountMeta {
5063                pubkey: source_pubkey,
5064                is_signer: true,
5065                is_writable: true,
5066            },
5067        ];
5068        process_instruction(
5069            VoteProgramFeatures::all_enabled(),
5070            &instruction_data,
5071            vec![
5072                (vote_pubkey_v3, vote_account_v3),
5073                (source_pubkey, source_account.clone()),
5074            ],
5075            instruction_accounts_v3,
5076            Err(InstructionError::InvalidAccountData),
5077        );
5078
5079        // Fail - non-system-owned source account.
5080        let non_system_source_account = AccountSharedData::new(1_000_000, 0, &Pubkey::new_unique());
5081        process_instruction_with_cu_check(
5082            VoteProgramFeatures::all_enabled(),
5083            &instruction_data,
5084            vec![
5085                (vote_pubkey, vote_account_v4.clone()),
5086                (source_pubkey, non_system_source_account),
5087                (
5088                    solana_sdk_ids::system_program::id(),
5089                    AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id()),
5090                ),
5091            ],
5092            instruction_accounts.clone(),
5093            Err(InstructionError::ExternalAccountLamportSpend),
5094            DEPOSIT_DELEGATOR_REWARDS_COMPUTE_UNITS,
5095        );
5096
5097        // Fail - source account == destination account.
5098        process_instruction_with_cu_check(
5099            VoteProgramFeatures::all_enabled(),
5100            &instruction_data,
5101            vec![
5102                (vote_pubkey, vote_account_v4.clone()),
5103                (
5104                    solana_sdk_ids::system_program::id(),
5105                    AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id()),
5106                ),
5107            ],
5108            vec![
5109                AccountMeta {
5110                    pubkey: vote_pubkey,
5111                    is_signer: false,
5112                    is_writable: true,
5113                },
5114                AccountMeta {
5115                    pubkey: vote_pubkey, // Duplicated
5116                    is_signer: true,
5117                    is_writable: true,
5118                },
5119                AccountMeta {
5120                    pubkey: solana_sdk_ids::system_program::id(),
5121                    is_signer: false,
5122                    is_writable: false,
5123                },
5124            ],
5125            Err(InstructionError::InvalidArgument),
5126            DEPOSIT_DELEGATOR_REWARDS_COMPUTE_UNITS,
5127        );
5128
5129        // Fail - source account has fewer lamports than the deposit.
5130        let underfunded_source_account =
5131            AccountSharedData::new(deposit_amount - 1, 0, &solana_sdk_ids::system_program::id());
5132        process_instruction_with_cu_check(
5133            VoteProgramFeatures::all_enabled(),
5134            &instruction_data,
5135            vec![
5136                (vote_pubkey, vote_account_v4.clone()),
5137                (source_pubkey, underfunded_source_account),
5138                (
5139                    solana_sdk_ids::system_program::id(),
5140                    AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id()),
5141                ),
5142            ],
5143            instruction_accounts.clone(),
5144            // SystemError::ResultWithNegativeLamports.
5145            Err(InstructionError::Custom(1)),
5146            DEPOSIT_DELEGATOR_REWARDS_COMPUTE_UNITS,
5147        );
5148
5149        // Fail - deposit overflow.
5150        let deposit_amount = 100_000;
5151        let mut vote_account_near_max = vote_account_v4.clone();
5152        {
5153            let mut vote_state =
5154                VoteStateV4::deserialize(vote_account_near_max.data(), &vote_pubkey).unwrap();
5155            vote_state.pending_delegator_rewards = u64::MAX - deposit_amount + 1;
5156            vote_account_near_max
5157                .set_data_from_slice(&VoteStateHandler::new_v4(vote_state).serialize());
5158        }
5159
5160        let instruction_data = wincode::serialize(&VoteInstruction::DepositDelegatorRewards {
5161            deposit: deposit_amount,
5162        })
5163        .unwrap();
5164
5165        process_instruction_with_cu_check(
5166            VoteProgramFeatures::all_enabled(),
5167            &instruction_data,
5168            vec![
5169                (vote_pubkey, vote_account_near_max),
5170                (source_pubkey, source_account.clone()),
5171                (
5172                    solana_sdk_ids::system_program::id(),
5173                    AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id()),
5174                ),
5175            ],
5176            instruction_accounts.clone(),
5177            Err(InstructionError::ArithmeticOverflow),
5178            DEPOSIT_DELEGATOR_REWARDS_COMPUTE_UNITS,
5179        );
5180
5181        // Success
5182        let resulting_accounts = process_instruction_with_cu_check(
5183            VoteProgramFeatures::all_enabled(),
5184            &instruction_data,
5185            transaction_accounts.clone(),
5186            instruction_accounts.clone(),
5187            Ok(()),
5188            DEPOSIT_DELEGATOR_REWARDS_COMPUTE_UNITS,
5189        );
5190
5191        // Vote account should have been credited `deposit_amount`.
5192        // Source account should have been debited `deposit_amount`.
5193        // Vote state's `pending_delegator_rewards` should be updated.
5194        let vote_account_starting_lamports = vote_account_v4.lamports();
5195        let source_account_starting_lamports = source_lamports;
5196        let resulting_vote_account = &resulting_accounts[0];
5197        let resulting_source_account = &resulting_accounts[1];
5198        let vote_state =
5199            deserialize_vote_state_for_test(resulting_vote_account.data(), &vote_pubkey);
5200        assert_eq!(
5201            resulting_vote_account.lamports(),
5202            vote_account_starting_lamports + deposit_amount,
5203        );
5204        assert_eq!(
5205            resulting_source_account.lamports(),
5206            source_account_starting_lamports - deposit_amount,
5207        );
5208        assert_eq!(
5209            vote_state.as_ref_v4().pending_delegator_rewards,
5210            deposit_amount,
5211        );
5212
5213        // Run it again with a new deposit amount.
5214        let first_deposit_amount = deposit_amount;
5215        let second_deposit_amount = 250_000;
5216        let vote_account_starting_lamports = resulting_vote_account.lamports();
5217        let source_account_starting_lamports = resulting_source_account.lamports();
5218
5219        let instruction_data = wincode::serialize(&VoteInstruction::DepositDelegatorRewards {
5220            deposit: second_deposit_amount,
5221        })
5222        .unwrap();
5223
5224        let resulting_accounts = process_instruction_with_cu_check(
5225            VoteProgramFeatures::all_enabled(),
5226            &instruction_data,
5227            vec![
5228                (vote_pubkey, resulting_vote_account.clone()),
5229                (source_pubkey, resulting_source_account.clone()),
5230                (
5231                    solana_sdk_ids::system_program::id(),
5232                    AccountSharedData::new(0, 0, &solana_sdk_ids::native_loader::id()),
5233                ),
5234            ],
5235            instruction_accounts.clone(),
5236            Ok(()),
5237            DEPOSIT_DELEGATOR_REWARDS_COMPUTE_UNITS,
5238        );
5239
5240        let resulting_vote_account = &resulting_accounts[0];
5241        let resulting_source_account = &resulting_accounts[1];
5242        let vote_state =
5243            deserialize_vote_state_for_test(resulting_vote_account.data(), &vote_pubkey);
5244        assert_eq!(
5245            resulting_vote_account.lamports(),
5246            vote_account_starting_lamports + second_deposit_amount,
5247        );
5248        assert_eq!(
5249            resulting_source_account.lamports(),
5250            source_account_starting_lamports - second_deposit_amount,
5251        );
5252        assert_eq!(
5253            vote_state.as_ref_v4().pending_delegator_rewards,
5254            first_deposit_amount + second_deposit_amount,
5255        );
5256
5257        // Success - zero-lamport deposit.
5258        let vote_account_starting_lamports = vote_account_v4.lamports();
5259        let source_account_starting_lamports = source_lamports;
5260        let instruction_data =
5261            wincode::serialize(&VoteInstruction::DepositDelegatorRewards { deposit: 0 }).unwrap();
5262
5263        let resulting_accounts = process_instruction_with_cu_check(
5264            VoteProgramFeatures::all_enabled(),
5265            &instruction_data,
5266            transaction_accounts,
5267            instruction_accounts.clone(),
5268            Ok(()),
5269            DEPOSIT_DELEGATOR_REWARDS_COMPUTE_UNITS,
5270        );
5271
5272        let resulting_vote_account = &resulting_accounts[0];
5273        let resulting_source_account = &resulting_accounts[1];
5274        let vote_state =
5275            deserialize_vote_state_for_test(resulting_vote_account.data(), &vote_pubkey);
5276        assert_eq!(
5277            resulting_vote_account.lamports(),
5278            vote_account_starting_lamports, // No-op
5279        );
5280        assert_eq!(
5281            resulting_source_account.lamports(),
5282            source_account_starting_lamports, // No-op
5283        );
5284        assert_eq!(
5285            vote_state.as_ref_v4().pending_delegator_rewards,
5286            0, // No-op
5287        );
5288    }
5289
5290    #[test]
5291    #[allow(clippy::arithmetic_side_effects)]
5292    fn test_withdraw_pending_delegator_rewards() {
5293        let rent_sysvar = Rent::default();
5294        let rent_minimum_balance = rent_sysvar.minimum_balance(VoteStateV4::size_of());
5295
5296        let pending_rewards = 500_000;
5297        let extra_for_withdraw = 100_000;
5298        let vote_account_lamports = rent_minimum_balance + pending_rewards + extra_for_withdraw;
5299
5300        let (vote_pubkey, _authorized_voter, authorized_withdrawer, mut vote_account) =
5301            create_test_account_with_authorized();
5302
5303        // Set some pending delegator rewards.
5304        {
5305            let mut vote_state =
5306                VoteStateV4::deserialize(vote_account.data(), &vote_pubkey).unwrap();
5307            vote_state.pending_delegator_rewards = pending_rewards;
5308            vote_account.set_data_from_slice(&VoteStateHandler::new_v4(vote_state).serialize());
5309            vote_account.set_lamports(vote_account_lamports);
5310        };
5311
5312        let features = VoteProgramFeatures::all_enabled();
5313
5314        let instruction_accounts = vec![
5315            AccountMeta {
5316                pubkey: vote_pubkey,
5317                is_signer: false,
5318                is_writable: true,
5319            },
5320            AccountMeta {
5321                pubkey: authorized_withdrawer,
5322                is_signer: true,
5323                is_writable: true,
5324            },
5325        ];
5326
5327        let rent_account = create_sysvar_account(&rent_sysvar);
5328        let transaction_accounts = vec![
5329            (vote_pubkey, vote_account.clone()),
5330            (authorized_withdrawer, AccountSharedData::default()),
5331            (sysvar::clock::id(), create_default_clock_account()),
5332            (sysvar::rent::id(), rent_account.clone()),
5333        ];
5334
5335        // Should fail, can't close vote account when
5336        // pending_delegator_rewards > 0.
5337        process_instruction(
5338            features,
5339            &wincode::serialize(&VoteInstruction::Withdraw(vote_account_lamports)).unwrap(),
5340            transaction_accounts.clone(),
5341            instruction_accounts.clone(),
5342            Err(InstructionError::InsufficientFunds),
5343        );
5344
5345        // Should fail, can't withdraw more than
5346        // (lamports - pending_delegator_rewards - rent_exempt).
5347        process_instruction(
5348            features,
5349            &wincode::serialize(&VoteInstruction::Withdraw(vote_account_lamports + 1)).unwrap(),
5350            transaction_accounts.clone(),
5351            instruction_accounts.clone(),
5352            Err(InstructionError::InsufficientFunds),
5353        );
5354
5355        // Should pass, can withdraw up to the max withdrawable amount.
5356        for i in 1..10 {
5357            let withdraw_amount = 1 + i * extra_for_withdraw / 10;
5358
5359            let accounts = process_instruction(
5360                features,
5361                &wincode::serialize(&VoteInstruction::Withdraw(withdraw_amount)).unwrap(),
5362                transaction_accounts.clone(),
5363                instruction_accounts.clone(),
5364                Ok(()),
5365            );
5366
5367            assert_eq!(
5368                accounts[0].lamports(),
5369                vote_account_lamports - withdraw_amount
5370            );
5371            assert!(accounts[0].lamports() >= rent_minimum_balance + pending_rewards);
5372            assert_eq!(accounts[1].lamports(), withdraw_amount);
5373        }
5374
5375        // Now clear pending delegator rewards.
5376        {
5377            let mut vote_state =
5378                VoteStateV4::deserialize(vote_account.data(), &vote_pubkey).unwrap();
5379            vote_state.pending_delegator_rewards = 0;
5380            vote_account.set_data_from_slice(&VoteStateHandler::new_v4(vote_state).serialize());
5381            vote_account.set_lamports(vote_account_lamports);
5382        };
5383
5384        // Should pass, no pending delegator rewards, so we can close the whole
5385        // thing out.
5386        let accounts = process_instruction(
5387            features,
5388            &wincode::serialize(&VoteInstruction::Withdraw(vote_account_lamports)).unwrap(),
5389            vec![
5390                (vote_pubkey, vote_account.clone()),
5391                (authorized_withdrawer, AccountSharedData::default()),
5392                (sysvar::clock::id(), create_default_clock_account()),
5393                (sysvar::rent::id(), rent_account),
5394            ],
5395            instruction_accounts.clone(),
5396            Ok(()),
5397        );
5398
5399        assert_eq!(accounts[0].lamports(), 0);
5400        assert_eq!(accounts[0].data(), vec![0; VoteStateV4::size_of()]);
5401        assert_eq!(accounts[1].lamports(), vote_account_lamports);
5402    }
5403}