Skip to main content

solana_vote_program/
vote_processor.rs

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