Skip to main content

solana_vote_program/
vote_processor.rs

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