Skip to main content

solana_vote_program/
vote_processor.rs

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