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