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