1use crate::{
5 id,
6 vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState},
7};
8use log::*;
9use num_derive::{FromPrimitive, ToPrimitive};
10use serde_derive::{Deserialize, Serialize};
11use gemachain_metrics::inc_new_counter_info;
12use gemachain_sdk::{
13 decode_error::DecodeError,
14 feature_set,
15 hash::Hash,
16 instruction::{AccountMeta, Instruction, InstructionError},
17 keyed_account::{from_keyed_account, get_signers, keyed_account_at_index, KeyedAccount},
18 process_instruction::InvokeContext,
19 program_utils::limited_deserialize,
20 pubkey::Pubkey,
21 system_instruction,
22 sysvar::{self, clock::Clock, slot_hashes::SlotHashes},
23};
24use std::collections::HashSet;
25use thiserror::Error;
26
27#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
29pub enum VoteError {
30 #[error("vote already recorded or not in slot hashes history")]
31 VoteTooOld,
32
33 #[error("vote slots do not match bank history")]
34 SlotsMismatch,
35
36 #[error("vote hash does not match bank hash")]
37 SlotHashMismatch,
38
39 #[error("vote has no slots, invalid")]
40 EmptySlots,
41
42 #[error("vote timestamp not recent")]
43 TimestampTooOld,
44
45 #[error("authorized voter has already been changed this epoch")]
46 TooSoonToReauthorize,
47}
48
49impl<E> DecodeError<E> for VoteError {
50 fn type_of() -> &'static str {
51 "VoteError"
52 }
53}
54
55#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
56pub enum VoteInstruction {
57 InitializeAccount(VoteInit),
65
66 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),
126}
127
128fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
129 let account_metas = vec![
130 AccountMeta::new(*vote_pubkey, false),
131 AccountMeta::new_readonly(sysvar::rent::id(), false),
132 AccountMeta::new_readonly(sysvar::clock::id(), false),
133 AccountMeta::new_readonly(vote_init.node_pubkey, true),
134 ];
135
136 Instruction::new_with_bincode(
137 id(),
138 &VoteInstruction::InitializeAccount(*vote_init),
139 account_metas,
140 )
141}
142
143pub fn create_account(
144 from_pubkey: &Pubkey,
145 vote_pubkey: &Pubkey,
146 vote_init: &VoteInit,
147 carats: u64,
148) -> Vec<Instruction> {
149 let space = VoteState::size_of() as u64;
150 let create_ix =
151 system_instruction::create_account(from_pubkey, vote_pubkey, carats, space, &id());
152 let init_ix = initialize_account(vote_pubkey, vote_init);
153 vec![create_ix, init_ix]
154}
155
156pub fn create_account_with_seed(
157 from_pubkey: &Pubkey,
158 vote_pubkey: &Pubkey,
159 base: &Pubkey,
160 seed: &str,
161 vote_init: &VoteInit,
162 carats: u64,
163) -> Vec<Instruction> {
164 let space = VoteState::size_of() as u64;
165 let create_ix = system_instruction::create_account_with_seed(
166 from_pubkey,
167 vote_pubkey,
168 base,
169 seed,
170 carats,
171 space,
172 &id(),
173 );
174 let init_ix = initialize_account(vote_pubkey, vote_init);
175 vec![create_ix, init_ix]
176}
177
178pub fn authorize(
179 vote_pubkey: &Pubkey,
180 authorized_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey,
182 vote_authorize: VoteAuthorize,
183) -> Instruction {
184 let account_metas = vec![
185 AccountMeta::new(*vote_pubkey, false),
186 AccountMeta::new_readonly(sysvar::clock::id(), false),
187 AccountMeta::new_readonly(*authorized_pubkey, true),
188 ];
189
190 Instruction::new_with_bincode(
191 id(),
192 &VoteInstruction::Authorize(*new_authorized_pubkey, vote_authorize),
193 account_metas,
194 )
195}
196
197pub fn authorize_checked(
198 vote_pubkey: &Pubkey,
199 authorized_pubkey: &Pubkey, new_authorized_pubkey: &Pubkey,
201 vote_authorize: VoteAuthorize,
202) -> Instruction {
203 let account_metas = vec![
204 AccountMeta::new(*vote_pubkey, false),
205 AccountMeta::new_readonly(sysvar::clock::id(), false),
206 AccountMeta::new_readonly(*authorized_pubkey, true),
207 AccountMeta::new_readonly(*new_authorized_pubkey, true),
208 ];
209
210 Instruction::new_with_bincode(
211 id(),
212 &VoteInstruction::AuthorizeChecked(vote_authorize),
213 account_metas,
214 )
215}
216
217pub fn update_validator_identity(
218 vote_pubkey: &Pubkey,
219 authorized_withdrawer_pubkey: &Pubkey,
220 node_pubkey: &Pubkey,
221) -> Instruction {
222 let account_metas = vec![
223 AccountMeta::new(*vote_pubkey, false),
224 AccountMeta::new_readonly(*node_pubkey, true),
225 AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
226 ];
227
228 Instruction::new_with_bincode(
229 id(),
230 &VoteInstruction::UpdateValidatorIdentity,
231 account_metas,
232 )
233}
234
235pub fn update_commission(
236 vote_pubkey: &Pubkey,
237 authorized_withdrawer_pubkey: &Pubkey,
238 commission: u8,
239) -> Instruction {
240 let account_metas = vec![
241 AccountMeta::new(*vote_pubkey, false),
242 AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
243 ];
244
245 Instruction::new_with_bincode(
246 id(),
247 &VoteInstruction::UpdateCommission(commission),
248 account_metas,
249 )
250}
251
252pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote) -> Instruction {
253 let account_metas = vec![
254 AccountMeta::new(*vote_pubkey, false),
255 AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
256 AccountMeta::new_readonly(sysvar::clock::id(), false),
257 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
258 ];
259
260 Instruction::new_with_bincode(id(), &VoteInstruction::Vote(vote), account_metas)
261}
262
263pub fn vote_switch(
264 vote_pubkey: &Pubkey,
265 authorized_voter_pubkey: &Pubkey,
266 vote: Vote,
267 proof_hash: Hash,
268) -> Instruction {
269 let account_metas = vec![
270 AccountMeta::new(*vote_pubkey, false),
271 AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
272 AccountMeta::new_readonly(sysvar::clock::id(), false),
273 AccountMeta::new_readonly(*authorized_voter_pubkey, true),
274 ];
275
276 Instruction::new_with_bincode(
277 id(),
278 &VoteInstruction::VoteSwitch(vote, proof_hash),
279 account_metas,
280 )
281}
282
283pub fn withdraw(
284 vote_pubkey: &Pubkey,
285 authorized_withdrawer_pubkey: &Pubkey,
286 carats: u64,
287 to_pubkey: &Pubkey,
288) -> Instruction {
289 let account_metas = vec![
290 AccountMeta::new(*vote_pubkey, false),
291 AccountMeta::new(*to_pubkey, false),
292 AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
293 ];
294
295 Instruction::new_with_bincode(id(), &VoteInstruction::Withdraw(carats), account_metas)
296}
297
298fn verify_rent_exemption(
299 keyed_account: &KeyedAccount,
300 rent_sysvar_account: &KeyedAccount,
301) -> Result<(), InstructionError> {
302 let rent: sysvar::rent::Rent = from_keyed_account(rent_sysvar_account)?;
303 if !rent.is_exempt(keyed_account.carats()?, keyed_account.data_len()?) {
304 Err(InstructionError::InsufficientFunds)
305 } else {
306 Ok(())
307 }
308}
309
310pub fn process_instruction(
311 _program_id: &Pubkey,
312 data: &[u8],
313 invoke_context: &mut dyn InvokeContext,
314) -> Result<(), InstructionError> {
315 let keyed_accounts = invoke_context.get_keyed_accounts()?;
316
317 trace!("process_instruction: {:?}", data);
318 trace!("keyed_accounts: {:?}", keyed_accounts);
319
320 let signers: HashSet<Pubkey> = get_signers(keyed_accounts);
321
322 let me = &mut keyed_account_at_index(keyed_accounts, 0)?;
323
324 if me.owner()? != id() {
325 return Err(InstructionError::InvalidAccountOwner);
326 }
327
328 match limited_deserialize(data)? {
329 VoteInstruction::InitializeAccount(vote_init) => {
330 verify_rent_exemption(me, keyed_account_at_index(keyed_accounts, 1)?)?;
331 vote_state::initialize_account(
332 me,
333 &vote_init,
334 &signers,
335 &from_keyed_account::<Clock>(keyed_account_at_index(keyed_accounts, 2)?)?,
336 invoke_context.is_feature_active(&feature_set::check_init_vote_data::id()),
337 )
338 }
339 VoteInstruction::Authorize(voter_pubkey, vote_authorize) => vote_state::authorize(
340 me,
341 &voter_pubkey,
342 vote_authorize,
343 &signers,
344 &from_keyed_account::<Clock>(keyed_account_at_index(keyed_accounts, 1)?)?,
345 ),
346 VoteInstruction::UpdateValidatorIdentity => vote_state::update_validator_identity(
347 me,
348 keyed_account_at_index(keyed_accounts, 1)?.unsigned_key(),
349 &signers,
350 ),
351 VoteInstruction::UpdateCommission(commission) => {
352 vote_state::update_commission(me, commission, &signers)
353 }
354 VoteInstruction::Vote(vote) | VoteInstruction::VoteSwitch(vote, _) => {
355 inc_new_counter_info!("vote-native", 1);
356 vote_state::process_vote(
357 me,
358 &from_keyed_account::<SlotHashes>(keyed_account_at_index(keyed_accounts, 1)?)?,
359 &from_keyed_account::<Clock>(keyed_account_at_index(keyed_accounts, 2)?)?,
360 &vote,
361 &signers,
362 )
363 }
364 VoteInstruction::Withdraw(carats) => {
365 let to = keyed_account_at_index(keyed_accounts, 1)?;
366 vote_state::withdraw(me, carats, to, &signers)
367 }
368 VoteInstruction::AuthorizeChecked(vote_authorize) => {
369 if invoke_context.is_feature_active(&feature_set::vote_stake_checked_instructions::id())
370 {
371 let voter_pubkey = &keyed_account_at_index(keyed_accounts, 3)?
372 .signer_key()
373 .ok_or(InstructionError::MissingRequiredSignature)?;
374 vote_state::authorize(
375 me,
376 voter_pubkey,
377 vote_authorize,
378 &signers,
379 &from_keyed_account::<Clock>(keyed_account_at_index(keyed_accounts, 1)?)?,
380 )
381 } else {
382 Err(InstructionError::InvalidInstructionData)
383 }
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use bincode::serialize;
392 use gemachain_sdk::{
393 account::{self, Account, AccountSharedData},
394 process_instruction::MockInvokeContext,
395 rent::Rent,
396 };
397 use std::cell::RefCell;
398 use std::str::FromStr;
399
400 fn create_default_account() -> RefCell<AccountSharedData> {
401 RefCell::new(AccountSharedData::default())
402 }
403
404 #[test]
406 fn test_vote_process_instruction_decode_bail() {
407 assert_eq!(
408 super::process_instruction(
409 &Pubkey::default(),
410 &[],
411 &mut MockInvokeContext::new(vec![])
412 ),
413 Err(InstructionError::NotEnoughAccountKeys),
414 );
415 }
416
417 #[allow(clippy::same_item_push)]
418 fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> {
419 let mut accounts: Vec<_> = instruction
420 .accounts
421 .iter()
422 .map(|meta| {
423 RefCell::new(if sysvar::clock::check_id(&meta.pubkey) {
424 account::create_account_shared_data_for_test(&Clock::default())
425 } else if sysvar::slot_hashes::check_id(&meta.pubkey) {
426 account::create_account_shared_data_for_test(&SlotHashes::default())
427 } else if sysvar::rent::check_id(&meta.pubkey) {
428 account::create_account_shared_data_for_test(&Rent::free())
429 } else if meta.pubkey == invalid_vote_state_pubkey() {
430 AccountSharedData::from(Account {
431 owner: invalid_vote_state_pubkey(),
432 ..Account::default()
433 })
434 } else {
435 AccountSharedData::from(Account {
436 owner: id(),
437 ..Account::default()
438 })
439 })
440 })
441 .collect();
442
443 for _ in 0..instruction.accounts.len() {
444 accounts.push(RefCell::new(AccountSharedData::default()));
445 }
446 {
447 let keyed_accounts: Vec<_> = instruction
448 .accounts
449 .iter()
450 .zip(accounts.iter())
451 .map(|(meta, account)| KeyedAccount::new(&meta.pubkey, meta.is_signer, account))
452 .collect();
453 super::process_instruction(
454 &Pubkey::default(),
455 &instruction.data,
456 &mut MockInvokeContext::new(keyed_accounts),
457 )
458 }
459 }
460
461 fn invalid_vote_state_pubkey() -> Pubkey {
462 Pubkey::from_str("BadVote111111111111111111111111111111111111").unwrap()
463 }
464
465 #[test]
466 fn test_spoofed_vote() {
467 assert_eq!(
468 process_instruction(&vote(
469 &invalid_vote_state_pubkey(),
470 &Pubkey::default(),
471 Vote::default(),
472 )),
473 Err(InstructionError::InvalidAccountOwner),
474 );
475 }
476
477 #[test]
478 fn test_vote_process_instruction() {
479 gemachain_logger::setup();
480 let instructions = create_account(
481 &Pubkey::default(),
482 &Pubkey::default(),
483 &VoteInit::default(),
484 100,
485 );
486 assert_eq!(
487 process_instruction(&instructions[1]),
488 Err(InstructionError::InvalidAccountData),
489 );
490 assert_eq!(
491 process_instruction(&vote(
492 &Pubkey::default(),
493 &Pubkey::default(),
494 Vote::default(),
495 )),
496 Err(InstructionError::InvalidAccountData),
497 );
498 assert_eq!(
499 process_instruction(&vote_switch(
500 &Pubkey::default(),
501 &Pubkey::default(),
502 Vote::default(),
503 Hash::default(),
504 )),
505 Err(InstructionError::InvalidAccountData),
506 );
507 assert_eq!(
508 process_instruction(&authorize(
509 &Pubkey::default(),
510 &Pubkey::default(),
511 &Pubkey::default(),
512 VoteAuthorize::Voter,
513 )),
514 Err(InstructionError::InvalidAccountData),
515 );
516 assert_eq!(
517 process_instruction(&update_validator_identity(
518 &Pubkey::default(),
519 &Pubkey::default(),
520 &Pubkey::default(),
521 )),
522 Err(InstructionError::InvalidAccountData),
523 );
524 assert_eq!(
525 process_instruction(&update_commission(
526 &Pubkey::default(),
527 &Pubkey::default(),
528 0,
529 )),
530 Err(InstructionError::InvalidAccountData),
531 );
532
533 assert_eq!(
534 process_instruction(&withdraw(
535 &Pubkey::default(),
536 &Pubkey::default(),
537 0,
538 &Pubkey::default()
539 )),
540 Err(InstructionError::InvalidAccountData),
541 );
542 }
543
544 #[test]
545 fn test_vote_authorize_checked() {
546 let vote_pubkey = Pubkey::new_unique();
547 let authorized_pubkey = Pubkey::new_unique();
548 let new_authorized_pubkey = Pubkey::new_unique();
549
550 let mut instruction = authorize_checked(
552 &vote_pubkey,
553 &authorized_pubkey,
554 &new_authorized_pubkey,
555 VoteAuthorize::Voter,
556 );
557 instruction.accounts = instruction.accounts[0..2].to_vec();
558 assert_eq!(
559 process_instruction(&instruction),
560 Err(InstructionError::NotEnoughAccountKeys),
561 );
562
563 let mut instruction = authorize_checked(
564 &vote_pubkey,
565 &authorized_pubkey,
566 &new_authorized_pubkey,
567 VoteAuthorize::Withdrawer,
568 );
569 instruction.accounts = instruction.accounts[0..2].to_vec();
570 assert_eq!(
571 process_instruction(&instruction),
572 Err(InstructionError::NotEnoughAccountKeys),
573 );
574
575 let mut instruction = authorize_checked(
577 &vote_pubkey,
578 &authorized_pubkey,
579 &new_authorized_pubkey,
580 VoteAuthorize::Voter,
581 );
582 instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false);
583 assert_eq!(
584 process_instruction(&instruction),
585 Err(InstructionError::MissingRequiredSignature),
586 );
587
588 let mut instruction = authorize_checked(
589 &vote_pubkey,
590 &authorized_pubkey,
591 &new_authorized_pubkey,
592 VoteAuthorize::Withdrawer,
593 );
594 instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false);
595 assert_eq!(
596 process_instruction(&instruction),
597 Err(InstructionError::MissingRequiredSignature),
598 );
599
600 let vote_account = AccountSharedData::new_ref(100, VoteState::size_of(), &id());
602 let clock_address = sysvar::clock::id();
603 let clock_account = RefCell::new(account::create_account_shared_data_for_test(
604 &Clock::default(),
605 ));
606 let default_authorized_pubkey = Pubkey::default();
607 let authorized_account = create_default_account();
608 let new_authorized_account = create_default_account();
609 let keyed_accounts = vec![
610 KeyedAccount::new(&vote_pubkey, false, &vote_account),
611 KeyedAccount::new(&clock_address, false, &clock_account),
612 KeyedAccount::new(&default_authorized_pubkey, true, &authorized_account),
613 KeyedAccount::new(&new_authorized_pubkey, true, &new_authorized_account),
614 ];
615 assert_eq!(
616 super::process_instruction(
617 &Pubkey::default(),
618 &serialize(&VoteInstruction::AuthorizeChecked(VoteAuthorize::Voter)).unwrap(),
619 &mut MockInvokeContext::new(keyed_accounts)
620 ),
621 Ok(())
622 );
623
624 let keyed_accounts = vec![
625 KeyedAccount::new(&vote_pubkey, false, &vote_account),
626 KeyedAccount::new(&clock_address, false, &clock_account),
627 KeyedAccount::new(&default_authorized_pubkey, true, &authorized_account),
628 KeyedAccount::new(&new_authorized_pubkey, true, &new_authorized_account),
629 ];
630 assert_eq!(
631 super::process_instruction(
632 &Pubkey::default(),
633 &serialize(&VoteInstruction::AuthorizeChecked(
634 VoteAuthorize::Withdrawer
635 ))
636 .unwrap(),
637 &mut MockInvokeContext::new(keyed_accounts)
638 ),
639 Ok(())
640 );
641 }
642
643 #[test]
644 fn test_minimum_balance() {
645 let rent = gemachain_sdk::rent::Rent::default();
646 let minimum_balance = rent.minimum_balance(VoteState::size_of());
647 assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
649 }
650
651 #[test]
652 fn test_custom_error_decode() {
653 use num_traits::FromPrimitive;
654 fn pretty_err<T>(err: InstructionError) -> String
655 where
656 T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
657 {
658 if let InstructionError::Custom(code) = err {
659 let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
660 format!(
661 "{:?}: {}::{:?} - {}",
662 err,
663 T::type_of(),
664 specific_error,
665 specific_error,
666 )
667 } else {
668 "".to_string()
669 }
670 }
671 assert_eq!(
672 "Custom(0): VoteError::VoteTooOld - vote already recorded or not in slot hashes history",
673 pretty_err::<VoteError>(VoteError::VoteTooOld.into())
674 )
675 }
676}