1#[cfg(feature = "frozen-abi")]
4use solana_frozen_abi_macro::{frozen_abi, StableAbi, StableAbiSample};
5use {
6 super::state::TowerSync,
7 crate::state::{
8 Vote, VoteAuthorize, VoteAuthorizeCheckedWithSeedArgs, VoteAuthorizeWithSeedArgs, VoteInit,
9 VoteInitV2, VoteStateUpdate, VoteStateV4,
10 },
11 solana_clock::{Slot, UnixTimestamp},
12 solana_hash::Hash,
13 solana_pubkey::Pubkey,
14};
15#[cfg(feature = "bincode")]
16use {
17 crate::program::id,
18 solana_instruction::{AccountMeta, Instruction},
19 solana_sdk_ids::sysvar,
20};
21#[cfg(feature = "wincode")]
22use {
23 crate::state::wincode_compact::{CompactTowerSync, CompactVoteStateUpdate},
24 wincode::{SchemaRead, SchemaWrite},
25};
26#[cfg(feature = "serde")]
27use {
28 crate::state::{serde_compact_vote_state_update, serde_tower_sync},
29 serde_derive::{Deserialize, Serialize},
30};
31
32#[repr(u8)]
33#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
34#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
35#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
36#[derive(Debug, PartialEq, Eq, Clone)]
37pub enum CommissionKind {
38 InflationRewards = 0,
39 BlockRevenue = 1,
40}
41
42#[cfg_attr(
43 feature = "frozen-abi",
44 frozen_abi(
45 abi_digest = "9bqZ5L1KnMFnjQPMoRtfhdgUok3G5W2KKRGuw8uwbkuY",
46 abi_serializer = ["bincode", "wincode"]
47 ),
48 derive(StableAbi, StableAbiSample)
49)]
50#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
51#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead))]
52#[derive(Debug, PartialEq, Eq, Clone)]
53pub enum VoteInstruction {
54 InitializeAccount(VoteInit),
62
63 Authorize(Pubkey, VoteAuthorize),
73
74 Vote(Vote),
82
83 Withdraw(u64),
90
91 UpdateValidatorIdentity,
98
99 UpdateCommission(u8),
105
106 VoteSwitch(Vote, Hash),
114
115 AuthorizeChecked(VoteAuthorize),
129
130 UpdateVoteState(VoteStateUpdate),
136
137 UpdateVoteStateSwitch(VoteStateUpdate, Hash),
143
144 AuthorizeWithSeed(VoteAuthorizeWithSeedArgs),
157
158 AuthorizeCheckedWithSeed(VoteAuthorizeCheckedWithSeedArgs),
175
176 #[cfg_attr(feature = "serde", serde(with = "serde_compact_vote_state_update"))]
182 CompactUpdateVoteState(
183 #[cfg_attr(feature = "wincode", wincode(with = "CompactVoteStateUpdate"))] VoteStateUpdate,
184 ),
185
186 CompactUpdateVoteStateSwitch(
192 #[cfg_attr(feature = "serde", serde(with = "serde_compact_vote_state_update"))]
193 #[cfg_attr(feature = "wincode", wincode(with = "CompactVoteStateUpdate"))]
194 VoteStateUpdate,
195 Hash,
196 ),
197
198 #[cfg_attr(feature = "serde", serde(with = "serde_tower_sync"))]
204 TowerSync(#[cfg_attr(feature = "wincode", wincode(with = "CompactTowerSync"))] TowerSync),
205
206 TowerSyncSwitch(
212 #[cfg_attr(feature = "serde", serde(with = "serde_tower_sync"))]
213 #[cfg_attr(feature = "wincode", wincode(with = "CompactTowerSync"))]
214 TowerSync,
215 Hash,
216 ),
217
218 InitializeAccountV2(VoteInitV2),
234
235 UpdateCommissionCollector(CommissionKind),
244
245 UpdateCommissionBps {
252 commission_bps: u16,
253 kind: CommissionKind,
254 },
255
256 DepositDelegatorRewards { deposit: u64 },
262}
263
264impl VoteInstruction {
265 pub fn is_simple_vote(&self) -> bool {
266 matches!(
267 self,
268 Self::Vote(_)
269 | Self::VoteSwitch(_, _)
270 | Self::UpdateVoteState(_)
271 | Self::UpdateVoteStateSwitch(_, _)
272 | Self::CompactUpdateVoteState(_)
273 | Self::CompactUpdateVoteStateSwitch(_, _)
274 | Self::TowerSync(_)
275 | Self::TowerSyncSwitch(_, _),
276 )
277 }
278
279 pub fn is_single_vote_state_update(&self) -> bool {
280 matches!(
281 self,
282 Self::UpdateVoteState(_)
283 | Self::UpdateVoteStateSwitch(_, _)
284 | Self::CompactUpdateVoteState(_)
285 | Self::CompactUpdateVoteStateSwitch(_, _)
286 | Self::TowerSync(_)
287 | Self::TowerSyncSwitch(_, _),
288 )
289 }
290
291 pub fn last_voted_slot(&self) -> Option<Slot> {
293 assert!(self.is_simple_vote());
294 match self {
295 Self::Vote(v) | Self::VoteSwitch(v, _) => v.last_voted_slot(),
296 Self::UpdateVoteState(vote_state_update)
297 | Self::UpdateVoteStateSwitch(vote_state_update, _)
298 | Self::CompactUpdateVoteState(vote_state_update)
299 | Self::CompactUpdateVoteStateSwitch(vote_state_update, _) => {
300 vote_state_update.last_voted_slot()
301 }
302 Self::TowerSync(tower_sync) | Self::TowerSyncSwitch(tower_sync, _) => {
303 tower_sync.last_voted_slot()
304 }
305 _ => panic!("Tried to get slot on non simple vote instruction"),
306 }
307 }
308
309 pub fn hash(&self) -> Hash {
311 assert!(self.is_simple_vote());
312 let hash = match self {
313 Self::Vote(v) | Self::VoteSwitch(v, _) => &v.hash,
314 Self::UpdateVoteState(vote_state_update)
315 | Self::UpdateVoteStateSwitch(vote_state_update, _)
316 | Self::CompactUpdateVoteState(vote_state_update)
317 | Self::CompactUpdateVoteStateSwitch(vote_state_update, _) => &vote_state_update.hash,
318 Self::TowerSync(tower_sync) | Self::TowerSyncSwitch(tower_sync, _) => &tower_sync.hash,
319 _ => panic!("Tried to get hash on non simple vote instruction"),
320 };
321 Hash::new_from_array(hash.to_bytes())
322 }
323 pub fn timestamp(&self) -> Option<UnixTimestamp> {
325 assert!(self.is_simple_vote());
326 match self {
327 Self::Vote(v) | Self::VoteSwitch(v, _) => v.timestamp,
328 Self::UpdateVoteState(vote_state_update)
329 | Self::UpdateVoteStateSwitch(vote_state_update, _)
330 | Self::CompactUpdateVoteState(vote_state_update)
331 | Self::CompactUpdateVoteStateSwitch(vote_state_update, _) => {
332 vote_state_update.timestamp
333 }
334 Self::TowerSync(tower_sync) | Self::TowerSyncSwitch(tower_sync, _) => {
335 tower_sync.timestamp
336 }
337 _ => panic!("Tried to get timestamp on non simple vote instruction"),
338 }
339 }
340}
341
342#[cfg(feature = "bincode")]
343fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
344 let account_metas = vec![
345 AccountMeta::new(*vote_pubkey, false),
346 AccountMeta::new_readonly(sysvar::rent::id(), false),
347 AccountMeta::new_readonly(sysvar::clock::id(), false),
348 AccountMeta::new_readonly(vote_init.node_pubkey, true),
349 ];
350
351 Instruction::new_with_bincode(
352 id(),
353 &VoteInstruction::InitializeAccount(*vote_init),
354 account_metas,
355 )
356}
357
358#[cfg(feature = "bincode")]
359fn initialize_account_v2(
360 vote_pubkey: &Pubkey,
361 vote_init: &VoteInitV2,
362 inflation_rewards_collector: &Pubkey,
363 block_revenue_collector: &Pubkey,
364) -> Instruction {
365 let account_metas = vec![
366 AccountMeta::new(*vote_pubkey, false),
367 AccountMeta::new_readonly(vote_init.node_pubkey, true),
368 AccountMeta::new(*inflation_rewards_collector, false),
369 AccountMeta::new(*block_revenue_collector, false),
370 ];
371
372 Instruction::new_with_bincode(
373 id(),
374 &VoteInstruction::InitializeAccountV2(*vote_init),
375 account_metas,
376 )
377}
378
379pub struct CreateVoteAccountConfig<'a> {
380 pub space: u64,
381 pub with_seed: Option<(&'a Pubkey, &'a str)>,
382}
383
384impl Default for CreateVoteAccountConfig<'_> {
385 fn default() -> Self {
386 Self {
387 space: VoteStateV4::size_of() as u64,
389 with_seed: None,
390 }
391 }
392}
393
394#[cfg(feature = "bincode")]
395pub fn create_account_with_config(
396 from_pubkey: &Pubkey,
397 vote_pubkey: &Pubkey,
398 vote_init: &VoteInit,
399 lamports: u64,
400 config: CreateVoteAccountConfig,
401) -> Vec<Instruction> {
402 let create_ix = if let Some((base, seed)) = config.with_seed {
403 solana_system_interface::instruction::create_account_with_seed(
404 from_pubkey,
405 vote_pubkey,
406 base,
407 seed,
408 lamports,
409 config.space,
410 &id(),
411 )
412 } else {
413 solana_system_interface::instruction::create_account(
414 from_pubkey,
415 vote_pubkey,
416 lamports,
417 config.space,
418 &id(),
419 )
420 };
421 let init_ix = initialize_account(vote_pubkey, vote_init);
422 vec![create_ix, init_ix]
423}
424
425#[cfg(feature = "bincode")]
426pub fn create_account_with_config_v2(
427 from_pubkey: &Pubkey,
428 vote_pubkey: &Pubkey,
429 vote_init: &VoteInitV2,
430 inflation_rewards_collector: &Pubkey,
431 block_revenue_collector: &Pubkey,
432 lamports: u64,
433 config: CreateVoteAccountConfig,
434) -> Vec<Instruction> {
435 let create_ix = if let Some((base, seed)) = config.with_seed {
436 solana_system_interface::instruction::create_account_with_seed(
437 from_pubkey,
438 vote_pubkey,
439 base,
440 seed,
441 lamports,
442 config.space,
443 &id(),
444 )
445 } else {
446 solana_system_interface::instruction::create_account(
447 from_pubkey,
448 vote_pubkey,
449 lamports,
450 config.space,
451 &id(),
452 )
453 };
454 let init_ix = initialize_account_v2(
455 vote_pubkey,
456 vote_init,
457 inflation_rewards_collector,
458 block_revenue_collector,
459 );
460 vec![create_ix, init_ix]
461}
462
463#[cfg(feature = "bincode")]
464pub fn authorize(
465 vote_pubkey: &Pubkey,
466 authorized_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey,
468 vote_authorize: VoteAuthorize,
469) -> Instruction {
470 let account_metas = vec![
471 AccountMeta::new(*vote_pubkey, false),
472 AccountMeta::new_readonly(sysvar::clock::id(), false),
473 AccountMeta::new_readonly(*authorized_pubkey, true),
474 ];
475
476 Instruction::new_with_bincode(
477 id(),
478 &VoteInstruction::Authorize(*new_authorized_pubkey, vote_authorize),
479 account_metas,
480 )
481}
482
483#[cfg(feature = "bincode")]
484pub fn authorize_checked(
485 vote_pubkey: &Pubkey,
486 authorized_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey,
488 vote_authorize: VoteAuthorize,
489) -> Instruction {
490 let account_metas = vec![
491 AccountMeta::new(*vote_pubkey, false),
492 AccountMeta::new_readonly(sysvar::clock::id(), false),
493 AccountMeta::new_readonly(*authorized_pubkey, true),
494 AccountMeta::new_readonly(*new_authorized_pubkey, true),
495 ];
496
497 Instruction::new_with_bincode(
498 id(),
499 &VoteInstruction::AuthorizeChecked(vote_authorize),
500 account_metas,
501 )
502}
503
504#[cfg(feature = "bincode")]
505pub fn authorize_with_seed(
506 vote_pubkey: &Pubkey,
507 current_authority_base_key: &Pubkey,
508 current_authority_derived_key_owner: &Pubkey,
509 current_authority_derived_key_seed: &str,
510 new_authority: &Pubkey,
511 authorization_type: VoteAuthorize,
512) -> Instruction {
513 let account_metas = vec![
514 AccountMeta::new(*vote_pubkey, false),
515 AccountMeta::new_readonly(sysvar::clock::id(), false),
516 AccountMeta::new_readonly(*current_authority_base_key, true),
517 ];
518
519 Instruction::new_with_bincode(
520 id(),
521 &VoteInstruction::AuthorizeWithSeed(VoteAuthorizeWithSeedArgs {
522 authorization_type,
523 current_authority_derived_key_owner: *current_authority_derived_key_owner,
524 current_authority_derived_key_seed: current_authority_derived_key_seed.to_string(),
525 new_authority: *new_authority,
526 }),
527 account_metas,
528 )
529}
530
531#[cfg(feature = "bincode")]
532pub fn authorize_checked_with_seed(
533 vote_pubkey: &Pubkey,
534 current_authority_base_key: &Pubkey,
535 current_authority_derived_key_owner: &Pubkey,
536 current_authority_derived_key_seed: &str,
537 new_authority: &Pubkey,
538 authorization_type: VoteAuthorize,
539) -> Instruction {
540 let account_metas = vec![
541 AccountMeta::new(*vote_pubkey, false),
542 AccountMeta::new_readonly(sysvar::clock::id(), false),
543 AccountMeta::new_readonly(*current_authority_base_key, true),
544 AccountMeta::new_readonly(*new_authority, true),
545 ];
546
547 Instruction::new_with_bincode(
548 id(),
549 &VoteInstruction::AuthorizeCheckedWithSeed(VoteAuthorizeCheckedWithSeedArgs {
550 authorization_type,
551 current_authority_derived_key_owner: *current_authority_derived_key_owner,
552 current_authority_derived_key_seed: current_authority_derived_key_seed.to_string(),
553 }),
554 account_metas,
555 )
556}
557
558#[cfg(feature = "bincode")]
559pub fn update_validator_identity(
560 vote_pubkey: &Pubkey,
561 authorized_withdrawer_pubkey: &Pubkey,
562 node_pubkey: &Pubkey,
563) -> Instruction {
564 let account_metas = vec![
565 AccountMeta::new(*vote_pubkey, false),
566 AccountMeta::new_readonly(*node_pubkey, true),
567 AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
568 ];
569
570 Instruction::new_with_bincode(
571 id(),
572 &VoteInstruction::UpdateValidatorIdentity,
573 account_metas,
574 )
575}
576
577#[cfg(feature = "bincode")]
578pub fn update_commission(
579 vote_pubkey: &Pubkey,
580 authorized_withdrawer_pubkey: &Pubkey,
581 commission: u8,
582) -> Instruction {
583 let account_metas = vec![
584 AccountMeta::new(*vote_pubkey, false),
585 AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
586 ];
587
588 Instruction::new_with_bincode(
589 id(),
590 &VoteInstruction::UpdateCommission(commission),
591 account_metas,
592 )
593}
594
595#[cfg(feature = "bincode")]
596pub fn update_commission_collector(
597 vote_pubkey: &Pubkey,
598 authorized_withdrawer_pubkey: &Pubkey,
599 new_collector_pubkey: &Pubkey,
600 kind: CommissionKind,
601) -> Instruction {
602 let account_metas = vec![
603 AccountMeta::new(*vote_pubkey, false),
604 AccountMeta::new(*new_collector_pubkey, false),
605 AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
606 ];
607
608 Instruction::new_with_bincode(
609 id(),
610 &VoteInstruction::UpdateCommissionCollector(kind),
611 account_metas,
612 )
613}
614
615#[cfg(feature = "bincode")]
616pub fn update_commission_bps(
617 vote_pubkey: &Pubkey,
618 authorized_withdrawer_pubkey: &Pubkey,
619 kind: CommissionKind,
620 commission_bps: u16,
621) -> Instruction {
622 let account_metas = vec![
623 AccountMeta::new(*vote_pubkey, false),
624 AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
625 ];
626
627 Instruction::new_with_bincode(
628 id(),
629 &VoteInstruction::UpdateCommissionBps {
630 kind,
631 commission_bps,
632 },
633 account_metas,
634 )
635}
636
637#[cfg(feature = "bincode")]
638pub fn deposit_delegator_rewards(
639 vote_pubkey: &Pubkey,
640 source_pubkey: &Pubkey,
641 deposit: u64,
642) -> Instruction {
643 let account_metas = vec![
644 AccountMeta::new(*vote_pubkey, false),
645 AccountMeta::new(*source_pubkey, true),
646 ];
647
648 Instruction::new_with_bincode(
649 id(),
650 &VoteInstruction::DepositDelegatorRewards { deposit },
651 account_metas,
652 )
653}
654
655#[cfg(feature = "bincode")]
656pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote) -> Instruction {
657 let account_metas = vec![
658 AccountMeta::new(*vote_pubkey, false),
659 AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
660 AccountMeta::new_readonly(sysvar::clock::id(), false),
661 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
662 ];
663
664 Instruction::new_with_bincode(id(), &VoteInstruction::Vote(vote), account_metas)
665}
666
667#[cfg(feature = "bincode")]
668pub fn vote_switch(
669 vote_pubkey: &Pubkey,
670 authorized_voter_pubkey: &Pubkey,
671 vote: Vote,
672 proof_hash: Hash,
673) -> Instruction {
674 let account_metas = vec![
675 AccountMeta::new(*vote_pubkey, false),
676 AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
677 AccountMeta::new_readonly(sysvar::clock::id(), false),
678 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
679 ];
680
681 Instruction::new_with_bincode(
682 id(),
683 &VoteInstruction::VoteSwitch(vote, proof_hash),
684 account_metas,
685 )
686}
687
688#[cfg(feature = "bincode")]
689pub fn update_vote_state(
690 vote_pubkey: &Pubkey,
691 authorized_voter_pubkey: &Pubkey,
692 vote_state_update: VoteStateUpdate,
693) -> Instruction {
694 let account_metas = vec![
695 AccountMeta::new(*vote_pubkey, false),
696 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
697 ];
698
699 Instruction::new_with_bincode(
700 id(),
701 &VoteInstruction::UpdateVoteState(vote_state_update),
702 account_metas,
703 )
704}
705
706#[cfg(feature = "bincode")]
707pub fn update_vote_state_switch(
708 vote_pubkey: &Pubkey,
709 authorized_voter_pubkey: &Pubkey,
710 vote_state_update: VoteStateUpdate,
711 proof_hash: Hash,
712) -> Instruction {
713 let account_metas = vec![
714 AccountMeta::new(*vote_pubkey, false),
715 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
716 ];
717
718 Instruction::new_with_bincode(
719 id(),
720 &VoteInstruction::UpdateVoteStateSwitch(vote_state_update, proof_hash),
721 account_metas,
722 )
723}
724
725#[cfg(feature = "bincode")]
726pub fn compact_update_vote_state(
727 vote_pubkey: &Pubkey,
728 authorized_voter_pubkey: &Pubkey,
729 vote_state_update: VoteStateUpdate,
730) -> Instruction {
731 let account_metas = vec![
732 AccountMeta::new(*vote_pubkey, false),
733 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
734 ];
735
736 Instruction::new_with_bincode(
737 id(),
738 &VoteInstruction::CompactUpdateVoteState(vote_state_update),
739 account_metas,
740 )
741}
742
743#[cfg(feature = "bincode")]
744pub fn compact_update_vote_state_switch(
745 vote_pubkey: &Pubkey,
746 authorized_voter_pubkey: &Pubkey,
747 vote_state_update: VoteStateUpdate,
748 proof_hash: Hash,
749) -> Instruction {
750 let account_metas = vec![
751 AccountMeta::new(*vote_pubkey, false),
752 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
753 ];
754
755 Instruction::new_with_bincode(
756 id(),
757 &VoteInstruction::CompactUpdateVoteStateSwitch(vote_state_update, proof_hash),
758 account_metas,
759 )
760}
761
762#[cfg(feature = "bincode")]
763pub fn tower_sync(
764 vote_pubkey: &Pubkey,
765 authorized_voter_pubkey: &Pubkey,
766 tower_sync: TowerSync,
767) -> Instruction {
768 let account_metas = vec![
769 AccountMeta::new(*vote_pubkey, false),
770 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
771 ];
772
773 Instruction::new_with_bincode(id(), &VoteInstruction::TowerSync(tower_sync), account_metas)
774}
775
776#[cfg(feature = "bincode")]
777pub fn tower_sync_switch(
778 vote_pubkey: &Pubkey,
779 authorized_voter_pubkey: &Pubkey,
780 tower_sync: TowerSync,
781 proof_hash: Hash,
782) -> Instruction {
783 let account_metas = vec![
784 AccountMeta::new(*vote_pubkey, false),
785 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
786 ];
787
788 Instruction::new_with_bincode(
789 id(),
790 &VoteInstruction::TowerSyncSwitch(tower_sync, proof_hash),
791 account_metas,
792 )
793}
794
795#[cfg(feature = "bincode")]
796pub fn withdraw(
797 vote_pubkey: &Pubkey,
798 authorized_withdrawer_pubkey: &Pubkey,
799 lamports: u64,
800 to_pubkey: &Pubkey,
801) -> Instruction {
802 let account_metas = vec![
803 AccountMeta::new(*vote_pubkey, false),
804 AccountMeta::new(*to_pubkey, false),
805 AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
806 ];
807
808 Instruction::new_with_bincode(id(), &VoteInstruction::Withdraw(lamports), account_metas)
809}
810
811#[cfg(all(test, feature = "bincode"))]
812mod tests {
813 use {super::*, crate::state::Lockout, std::collections::VecDeque};
814
815 #[test]
816 fn test_compact_serialize_rejects_confirmation_count_above_u8() {
817 let lockouts = VecDeque::from([Lockout::new_with_confirmation_count(1, 256)]);
821 let vote_state_update = VoteStateUpdate::new(lockouts.clone(), None, Hash::default());
822 let tower_sync = TowerSync::new(lockouts, None, Hash::default(), Hash::default());
823
824 for ix in [
825 VoteInstruction::CompactUpdateVoteState(vote_state_update),
826 VoteInstruction::TowerSync(tower_sync),
827 ] {
828 let err = bincode::serialize(&ix).unwrap_err();
829 assert!(
830 err.to_string().contains("Invalid confirmation count"),
831 "unexpected error: {err}"
832 );
833 #[cfg(feature = "wincode")]
834 assert!(wincode::serialize(&ix).is_err());
835 }
836 }
837}