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