Skip to main content

solana_vote_program/
vote_processor.rs

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