1use {
4 crate::{
5 addins::max_voter_weight::{
6 assert_is_valid_max_voter_weight,
7 get_max_voter_weight_record_data_for_realm_and_governing_token_mint,
8 },
9 error::GovernanceError,
10 state::{
11 enums::{
12 GovernanceAccountType, InstructionExecutionFlags, MintMaxVoterWeightSource,
13 ProposalState, TransactionExecutionStatus, VoteThreshold, VoteTipping,
14 },
15 governance::GovernanceConfig,
16 legacy::ProposalV1,
17 proposal_transaction::ProposalTransactionV2,
18 realm::RealmV2,
19 realm_config::RealmConfigAccount,
20 vote_record::{Vote, VoteKind},
21 },
22 tools::spl_token::get_spl_token_mint_supply,
23 PROGRAM_AUTHORITY_SEED,
24 },
25 borsh::{maybestd::io::Write, BorshDeserialize, BorshSchema, BorshSerialize},
26 solana_program::{
27 account_info::{next_account_info, AccountInfo},
28 clock::{Slot, UnixTimestamp},
29 program_error::ProgramError,
30 program_pack::IsInitialized,
31 pubkey::Pubkey,
32 },
33 spl_governance_tools::account::{get_account_data, get_account_type, AccountMaxSize},
34 std::{cmp::Ordering, slice::Iter},
35};
36
37#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
39pub enum OptionVoteResult {
40 None,
42
43 Succeeded,
45
46 Defeated,
48}
49
50#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
52pub struct ProposalOption {
53 pub label: String,
55
56 pub vote_weight: u64,
58
59 pub vote_result: OptionVoteResult,
61
62 pub transactions_executed_count: u16,
64
65 pub transactions_count: u16,
67
68 pub transactions_next_index: u16,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
74pub enum VoteType {
75 SingleChoice,
82
83 MultiChoice {
88 #[allow(dead_code)]
90 choice_type: MultiChoiceType,
91
92 #[allow(dead_code)]
97 min_voter_options: u8,
98
99 #[allow(dead_code)]
105 max_voter_options: u8,
106
107 #[allow(dead_code)]
115 max_winning_options: u8,
116 },
117}
118
119#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
121pub enum MultiChoiceType {
122 FullWeight,
125
126 Weighted,
131}
132
133#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
135pub struct ProposalV2 {
136 pub account_type: GovernanceAccountType,
138
139 pub governance: Pubkey,
141
142 pub governing_token_mint: Pubkey,
146
147 pub state: ProposalState,
149
150 pub token_owner_record: Pubkey,
154
155 pub signatories_count: u8,
157
158 pub signatories_signed_off_count: u8,
160
161 pub vote_type: VoteType,
163
164 pub options: Vec<ProposalOption>,
166
167 pub deny_vote_weight: Option<u64>,
177
178 pub reserved1: u8,
181
182 pub abstain_vote_weight: Option<u64>,
185
186 pub start_voting_at: Option<UnixTimestamp>,
190
191 pub draft_at: UnixTimestamp,
193
194 pub signing_off_at: Option<UnixTimestamp>,
196
197 pub voting_at: Option<UnixTimestamp>,
199
200 pub voting_at_slot: Option<Slot>,
204
205 pub voting_completed_at: Option<UnixTimestamp>,
207
208 pub executing_at: Option<UnixTimestamp>,
210
211 pub closed_at: Option<UnixTimestamp>,
214
215 pub execution_flags: InstructionExecutionFlags,
218
219 pub max_vote_weight: Option<u64>,
225
226 pub max_voting_time: Option<u32>,
230
231 pub vote_threshold: Option<VoteThreshold>,
238
239 pub reserved: [u8; 64],
241
242 pub name: String,
244
245 pub description_link: String,
247
248 pub veto_vote_weight: u64,
250}
251
252impl AccountMaxSize for ProposalV2 {
253 fn get_max_size(&self) -> Option<usize> {
254 let options_size: usize = self.options.iter().map(|o| o.label.len() + 19).sum();
255 Some(self.name.len() + self.description_link.len() + options_size + 297)
256 }
257}
258
259impl IsInitialized for ProposalV2 {
260 fn is_initialized(&self) -> bool {
261 self.account_type == GovernanceAccountType::ProposalV2
262 }
263}
264
265impl ProposalV2 {
266 pub fn assert_can_edit_signatories(&self) -> Result<(), ProgramError> {
269 self.assert_is_draft_state()
270 .map_err(|_| GovernanceError::InvalidStateCannotEditSignatories.into())
271 }
272
273 pub fn assert_can_sign_off(&self) -> Result<(), ProgramError> {
275 match self.state {
276 ProposalState::Draft | ProposalState::SigningOff => Ok(()),
277 ProposalState::Executing
278 | ProposalState::ExecutingWithErrors
279 | ProposalState::Completed
280 | ProposalState::Cancelled
281 | ProposalState::Voting
282 | ProposalState::Succeeded
283 | ProposalState::Defeated
284 | ProposalState::Vetoed => Err(GovernanceError::InvalidStateCannotSignOff.into()),
285 }
286 }
287
288 fn assert_is_voting_state(&self) -> Result<(), ProgramError> {
290 if self.state != ProposalState::Voting {
291 return Err(GovernanceError::InvalidProposalState.into());
292 }
293
294 Ok(())
295 }
296
297 fn assert_is_draft_state(&self) -> Result<(), ProgramError> {
299 if self.state != ProposalState::Draft {
300 return Err(GovernanceError::InvalidProposalState.into());
301 }
302
303 Ok(())
304 }
305
306 pub fn assert_is_final_state(&self) -> Result<(), ProgramError> {
308 match self.state {
309 ProposalState::Completed
310 | ProposalState::Cancelled
311 | ProposalState::Defeated
312 | ProposalState::Vetoed => Ok(()),
313 ProposalState::Executing
314 | ProposalState::ExecutingWithErrors
315 | ProposalState::SigningOff
316 | ProposalState::Voting
317 | ProposalState::Draft
318 | ProposalState::Succeeded => Err(GovernanceError::InvalidStateNotFinal.into()),
319 }
320 }
321
322 pub fn assert_can_cast_vote(
324 &self,
325 config: &GovernanceConfig,
326 vote: &Vote,
327 current_unix_timestamp: UnixTimestamp,
328 ) -> Result<(), ProgramError> {
329 self.assert_is_voting_state()
330 .map_err(|_| GovernanceError::InvalidStateCannotVote)?;
331
332 if self.has_voting_max_time_ended(config, current_unix_timestamp) {
334 return Err(GovernanceError::ProposalVotingTimeExpired.into());
335 }
336
337 match vote {
338 Vote::Approve(_) | Vote::Abstain => {
339 if self.has_voting_base_time_ended(config, current_unix_timestamp) {
343 Err(GovernanceError::VoteNotAllowedInCoolOffTime.into())
344 } else {
345 Ok(())
346 }
347 }
348 Vote::Deny | Vote::Veto => Ok(()),
350 }
351 }
352
353 pub fn assert_can_refund_proposal_deposit(&self) -> Result<(), ProgramError> {
356 match self.state {
357 ProposalState::Succeeded
358 | ProposalState::Executing
359 | ProposalState::Completed
360 | ProposalState::Cancelled
361 | ProposalState::Defeated
362 | ProposalState::ExecutingWithErrors
363 | ProposalState::Vetoed => Ok(()),
364 ProposalState::Draft | ProposalState::SigningOff | ProposalState::Voting => {
365 Err(GovernanceError::CannotRefundProposalDeposit.into())
366 }
367 }
368 }
369
370 pub fn voting_base_time_end(&self, config: &GovernanceConfig) -> UnixTimestamp {
373 self.voting_at
374 .unwrap()
375 .checked_add(config.voting_base_time as i64)
376 .unwrap()
377 }
378
379 pub fn has_voting_base_time_ended(
381 &self,
382 config: &GovernanceConfig,
383 current_unix_timestamp: UnixTimestamp,
384 ) -> bool {
385 self.voting_base_time_end(config) < current_unix_timestamp
387 }
388
389 pub fn voting_max_time_end(&self, config: &GovernanceConfig) -> UnixTimestamp {
393 self.voting_base_time_end(config)
394 .checked_add(config.voting_cool_off_time as i64)
395 .unwrap()
396 }
397
398 pub fn has_voting_max_time_ended(
400 &self,
401 config: &GovernanceConfig,
402 current_unix_timestamp: UnixTimestamp,
403 ) -> bool {
404 self.voting_max_time_end(config) < current_unix_timestamp
406 }
407
408 pub fn assert_can_finalize_vote(
410 &self,
411 config: &GovernanceConfig,
412 current_unix_timestamp: UnixTimestamp,
413 ) -> Result<(), ProgramError> {
414 self.assert_is_voting_state()
415 .map_err(|_| GovernanceError::InvalidStateCannotFinalize)?;
416
417 if !self.has_voting_max_time_ended(config, current_unix_timestamp) {
420 return Err(GovernanceError::CannotFinalizeVotingInProgress.into());
421 }
422
423 Ok(())
424 }
425
426 pub fn finalize_vote(
430 &mut self,
431 max_voter_weight: u64,
432 config: &GovernanceConfig,
433 current_unix_timestamp: UnixTimestamp,
434 vote_threshold: &VoteThreshold,
435 ) -> Result<(), ProgramError> {
436 self.assert_can_finalize_vote(config, current_unix_timestamp)?;
437
438 self.state = self.resolve_final_vote_state(max_voter_weight, vote_threshold)?;
439 self.voting_completed_at = Some(self.voting_max_time_end(config));
440
441 self.max_vote_weight = Some(max_voter_weight);
443 self.vote_threshold = Some(vote_threshold.clone());
444
445 Ok(())
446 }
447
448 fn resolve_final_vote_state(
451 &mut self,
452 max_vote_weight: u64,
453 vote_threshold: &VoteThreshold,
454 ) -> Result<ProposalState, ProgramError> {
455 let min_vote_threshold_weight =
457 get_min_vote_threshold_weight(vote_threshold, max_vote_weight).unwrap();
458
459 let deny_vote_weight = self.deny_vote_weight.unwrap_or(0);
462
463 let mut best_succeeded_option_weight = 0;
464 let mut best_succeeded_option_count = 0u16;
465
466 for option in self.options.iter_mut() {
467 if option.vote_weight >= min_vote_threshold_weight
473 && option.vote_weight > deny_vote_weight
474 {
475 option.vote_result = OptionVoteResult::Succeeded;
476
477 match option.vote_weight.cmp(&best_succeeded_option_weight) {
478 Ordering::Greater => {
479 best_succeeded_option_weight = option.vote_weight;
480 best_succeeded_option_count = 1;
481 }
482 Ordering::Equal => {
483 best_succeeded_option_count =
484 best_succeeded_option_count.checked_add(1).unwrap()
485 }
486 Ordering::Less => {}
487 }
488 } else {
489 option.vote_result = OptionVoteResult::Defeated;
490 }
491 }
492
493 let mut final_state = if best_succeeded_option_count == 0 {
494 ProposalState::Defeated
497 } else {
498 match &self.vote_type {
499 VoteType::SingleChoice => {
500 let proposal_state = if best_succeeded_option_count > 1 {
501 best_succeeded_option_weight = u64::MAX; ProposalState::Defeated
505 } else {
506 ProposalState::Succeeded
507 };
508
509 for option in self.options.iter_mut() {
512 option.vote_result = if option.vote_weight == best_succeeded_option_weight {
513 OptionVoteResult::Succeeded
514 } else {
515 OptionVoteResult::Defeated
516 };
517 }
518
519 proposal_state
520 }
521 VoteType::MultiChoice {
522 choice_type: _,
523 max_voter_options: _,
524 max_winning_options: _,
525 min_voter_options: _,
526 } => {
527 ProposalState::Succeeded
530 }
531 }
532 };
533
534 if self.deny_vote_weight.is_none() {
545 final_state = ProposalState::Completed;
546 }
547
548 Ok(final_state)
549 }
550
551 fn get_max_voter_weight_from_mint_supply(
553 &mut self,
554 realm_data: &RealmV2,
555 governing_token_mint: &Pubkey,
556 governing_token_mint_supply: u64,
557 vote_kind: &VoteKind,
558 ) -> Result<u64, ProgramError> {
559 if Some(*governing_token_mint) == realm_data.config.council_mint {
561 return Ok(governing_token_mint_supply);
562 }
563
564 let max_voter_weight = match realm_data.config.community_mint_max_voter_weight_source {
565 MintMaxVoterWeightSource::SupplyFraction(fraction) => {
566 if fraction == MintMaxVoterWeightSource::SUPPLY_FRACTION_BASE {
567 return Ok(governing_token_mint_supply);
568 }
569
570 (governing_token_mint_supply as u128)
571 .checked_mul(fraction as u128)
572 .unwrap()
573 .checked_div(MintMaxVoterWeightSource::SUPPLY_FRACTION_BASE as u128)
574 .unwrap() as u64
575 }
576 MintMaxVoterWeightSource::Absolute(value) => value,
577 };
578
579 Ok(self.coerce_max_voter_weight(max_voter_weight, vote_kind))
583 }
584
585 fn coerce_max_voter_weight(&self, max_voter_weight: u64, vote_kind: &VoteKind) -> u64 {
587 let total_vote_weight = match vote_kind {
588 VoteKind::Electorate => {
589 let deny_vote_weight = self.deny_vote_weight.unwrap_or(0);
590
591 let max_option_vote_weight =
592 self.options.iter().map(|o| o.vote_weight).max().unwrap();
593
594 max_option_vote_weight
595 .checked_add(deny_vote_weight)
596 .unwrap()
597 }
598 VoteKind::Veto => self.veto_vote_weight,
599 };
600
601 max_voter_weight.max(total_vote_weight)
602 }
603
604 #[allow(clippy::too_many_arguments)]
607 pub fn resolve_max_voter_weight(
608 &mut self,
609 account_info_iter: &mut Iter<AccountInfo>,
610 realm: &Pubkey,
611 realm_data: &RealmV2,
612 realm_config_data: &RealmConfigAccount,
613 vote_governing_token_mint_info: &AccountInfo,
614 vote_kind: &VoteKind,
615 ) -> Result<u64, ProgramError> {
616 if let Some(max_voter_weight_addin) = realm_config_data
620 .get_token_config(realm_data, vote_governing_token_mint_info.key)?
621 .max_voter_weight_addin
622 {
623 let max_voter_weight_record_info = next_account_info(account_info_iter)?;
624
625 let max_voter_weight_record_data =
626 get_max_voter_weight_record_data_for_realm_and_governing_token_mint(
627 &max_voter_weight_addin,
628 max_voter_weight_record_info,
629 realm,
630 vote_governing_token_mint_info.key,
631 )?;
632
633 assert_is_valid_max_voter_weight(&max_voter_weight_record_data)?;
634
635 return Ok(self.coerce_max_voter_weight(
639 max_voter_weight_record_data.max_voter_weight,
640 vote_kind,
641 ));
642 }
643
644 let vote_governing_token_mint_supply =
645 get_spl_token_mint_supply(vote_governing_token_mint_info)?;
646
647 let max_voter_weight = self.get_max_voter_weight_from_mint_supply(
648 realm_data,
649 vote_governing_token_mint_info.key,
650 vote_governing_token_mint_supply,
651 vote_kind,
652 )?;
653
654 Ok(max_voter_weight)
655 }
656
657 pub fn try_tip_vote(
661 &mut self,
662 max_voter_weight: u64,
663 vote_tipping: &VoteTipping,
664 current_unix_timestamp: UnixTimestamp,
665 vote_threshold: &VoteThreshold,
666 vote_kind: &VoteKind,
667 ) -> Result<bool, ProgramError> {
668 if let Some(tipped_state) = self.try_get_tipped_vote_state(
669 max_voter_weight,
670 vote_tipping,
671 vote_threshold,
672 vote_kind,
673 ) {
674 self.state = tipped_state;
675 self.voting_completed_at = Some(current_unix_timestamp);
676
677 self.max_vote_weight = Some(max_voter_weight);
680 self.vote_threshold = Some(vote_threshold.clone());
681
682 Ok(true)
683 } else {
684 Ok(false)
685 }
686 }
687
688 pub fn try_get_tipped_vote_state(
692 &mut self,
693 max_voter_weight: u64,
694 vote_tipping: &VoteTipping,
695 vote_threshold: &VoteThreshold,
696 vote_kind: &VoteKind,
697 ) -> Option<ProposalState> {
698 let min_vote_threshold_weight =
699 get_min_vote_threshold_weight(vote_threshold, max_voter_weight).unwrap();
700
701 match vote_kind {
702 VoteKind::Electorate => self.try_get_tipped_electorate_vote_state(
703 max_voter_weight,
704 vote_tipping,
705 min_vote_threshold_weight,
706 ),
707 VoteKind::Veto => self.try_get_tipped_veto_vote_state(min_vote_threshold_weight),
708 }
709 }
710
711 fn try_get_tipped_electorate_vote_state(
715 &mut self,
716 max_voter_weight: u64,
717 vote_tipping: &VoteTipping,
718 min_vote_threshold_weight: u64,
719 ) -> Option<ProposalState> {
720 if self.vote_type != VoteType::SingleChoice
726 || self.deny_vote_weight.is_none()
729 || self.options.len() != 1
730 {
731 return None;
732 };
733
734 let yes_option = &mut self.options[0];
735
736 let yes_vote_weight = yes_option.vote_weight;
737 let deny_vote_weight = self.deny_vote_weight.unwrap();
738
739 match vote_tipping {
740 VoteTipping::Disabled => {}
741 VoteTipping::Strict => {
742 if yes_vote_weight >= min_vote_threshold_weight
743 && yes_vote_weight > (max_voter_weight.saturating_sub(yes_vote_weight))
744 {
745 yes_option.vote_result = OptionVoteResult::Succeeded;
746 return Some(ProposalState::Succeeded);
747 }
748 }
749 VoteTipping::Early => {
750 if yes_vote_weight >= min_vote_threshold_weight
751 && yes_vote_weight > deny_vote_weight
752 {
753 yes_option.vote_result = OptionVoteResult::Succeeded;
754 return Some(ProposalState::Succeeded);
755 }
756 }
757 }
758
759 if *vote_tipping != VoteTipping::Disabled
764 && (deny_vote_weight > (max_voter_weight.saturating_sub(min_vote_threshold_weight))
765 || deny_vote_weight >= (max_voter_weight.saturating_sub(deny_vote_weight)))
766 {
767 yes_option.vote_result = OptionVoteResult::Defeated;
768 return Some(ProposalState::Defeated);
769 }
770
771 None
772 }
773
774 fn try_get_tipped_veto_vote_state(
777 &mut self,
778 min_vote_threshold_weight: u64,
779 ) -> Option<ProposalState> {
780 if self.veto_vote_weight >= min_vote_threshold_weight {
784 Some(ProposalState::Vetoed)
787 } else {
788 None
789 }
790 }
791
792 pub fn assert_can_cancel(
794 &self,
795 config: &GovernanceConfig,
796 current_unix_timestamp: UnixTimestamp,
797 ) -> Result<(), ProgramError> {
798 match self.state {
799 ProposalState::Draft | ProposalState::SigningOff => Ok(()),
800 ProposalState::Voting => {
801 if self.has_voting_max_time_ended(config, current_unix_timestamp) {
805 return Err(GovernanceError::ProposalVotingTimeExpired.into());
806 }
807 Ok(())
808 }
809 ProposalState::Executing
810 | ProposalState::ExecutingWithErrors
811 | ProposalState::Completed
812 | ProposalState::Cancelled
813 | ProposalState::Succeeded
814 | ProposalState::Defeated
815 | ProposalState::Vetoed => {
816 Err(GovernanceError::InvalidStateCannotCancelProposal.into())
817 }
818 }
819 }
820
821 pub fn assert_can_edit_instructions(&self) -> Result<(), ProgramError> {
825 if self.assert_is_draft_state().is_err() {
826 return Err(GovernanceError::InvalidStateCannotEditTransactions.into());
827 }
828
829 if self.deny_vote_weight.is_none() {
832 return Err(GovernanceError::ProposalIsNotExecutable.into());
833 }
834
835 Ok(())
836 }
837
838 pub fn assert_can_execute_transaction(
841 &self,
842 proposal_transaction_data: &ProposalTransactionV2,
843 current_unix_timestamp: UnixTimestamp,
844 ) -> Result<(), ProgramError> {
845 match self.state {
846 ProposalState::Succeeded
847 | ProposalState::Executing
848 | ProposalState::ExecutingWithErrors => {}
849 ProposalState::Draft
850 | ProposalState::SigningOff
851 | ProposalState::Completed
852 | ProposalState::Voting
853 | ProposalState::Cancelled
854 | ProposalState::Defeated
855 | ProposalState::Vetoed => {
856 return Err(GovernanceError::InvalidStateCannotExecuteTransaction.into())
857 }
858 }
859
860 if self.options[proposal_transaction_data.option_index as usize].vote_result
861 != OptionVoteResult::Succeeded
862 {
863 return Err(GovernanceError::CannotExecuteDefeatedOption.into());
864 }
865
866 if self
867 .voting_completed_at
868 .unwrap()
869 .checked_add(proposal_transaction_data.hold_up_time as i64)
870 .unwrap()
871 >= current_unix_timestamp
872 {
873 return Err(GovernanceError::CannotExecuteTransactionWithinHoldUpTime.into());
874 }
875
876 if proposal_transaction_data.executed_at.is_some() {
877 return Err(GovernanceError::TransactionAlreadyExecuted.into());
878 }
879
880 Ok(())
881 }
882
883 pub fn assert_can_flag_transaction_error(
886 &self,
887 proposal_transaction_data: &ProposalTransactionV2,
888 current_unix_timestamp: UnixTimestamp,
889 ) -> Result<(), ProgramError> {
890 self.assert_can_execute_transaction(proposal_transaction_data, current_unix_timestamp)?;
892
893 if proposal_transaction_data.execution_status == TransactionExecutionStatus::Error {
894 return Err(GovernanceError::TransactionAlreadyFlaggedWithError.into());
895 }
896
897 Ok(())
898 }
899
900 pub fn assert_can_complete(&self) -> Result<(), ProgramError> {
903 if self.state != ProposalState::Succeeded {
905 return Err(GovernanceError::InvalidStateToCompleteProposal.into());
906 }
907
908 if self.options.iter().any(|o| o.transactions_count != 0) {
910 return Err(GovernanceError::InvalidStateToCompleteProposal.into());
911 }
912
913 Ok(())
914 }
915
916 pub fn assert_valid_vote(&self, vote: &Vote) -> Result<(), ProgramError> {
918 match vote {
919 Vote::Approve(choices) => {
920 if self.options.len() != choices.len() {
921 return Err(GovernanceError::InvalidNumberOfVoteChoices.into());
922 }
923
924 let mut choice_count = 0u16;
925 let mut total_choice_weight_percentage = 0u8;
926
927 for choice in choices {
928 if choice.rank > 0 {
929 return Err(GovernanceError::RankedVoteIsNotSupported.into());
930 }
931
932 if choice.weight_percentage > 0 {
933 choice_count = choice_count.checked_add(1).unwrap();
934
935 match self.vote_type {
936 VoteType::MultiChoice {
937 choice_type: MultiChoiceType::Weighted,
938 min_voter_options: _,
939 max_voter_options: _,
940 max_winning_options: _,
941 } => {
942 total_choice_weight_percentage = total_choice_weight_percentage
946 .checked_add(choice.weight_percentage)
947 .ok_or(GovernanceError::TotalVoteWeightMustBe100Percent)?;
948 }
949 _ => {
950 if choice.weight_percentage != 100 {
951 return Err(
952 GovernanceError::ChoiceWeightMustBe100Percent.into()
953 );
954 }
955 }
956 }
957 }
958 }
959
960 match self.vote_type {
961 VoteType::SingleChoice => {
962 if choice_count != 1 {
963 return Err(GovernanceError::SingleChoiceOnlyIsAllowed.into());
964 }
965 }
966 VoteType::MultiChoice {
967 choice_type: MultiChoiceType::FullWeight,
968 min_voter_options: _,
969 max_voter_options: _,
970 max_winning_options: _,
971 } => {
972 if choice_count == 0 {
973 return Err(GovernanceError::AtLeastSingleChoiceIsRequired.into());
974 }
975 }
976 VoteType::MultiChoice {
977 choice_type: MultiChoiceType::Weighted,
978 min_voter_options: _,
979 max_voter_options: _,
980 max_winning_options: _,
981 } => {
982 if choice_count == 0 {
983 return Err(GovernanceError::AtLeastSingleChoiceIsRequired.into());
984 }
985 if total_choice_weight_percentage != 100 {
986 return Err(GovernanceError::TotalVoteWeightMustBe100Percent.into());
987 }
988 }
989 }
990 }
991 Vote::Deny => {
992 if self.deny_vote_weight.is_none() {
993 return Err(GovernanceError::DenyVoteIsNotAllowed.into());
994 }
995 }
996 Vote::Abstain => {
997 return Err(GovernanceError::NotSupportedVoteType.into());
998 }
999 Vote::Veto => {}
1000 }
1001
1002 Ok(())
1003 }
1004
1005 pub fn serialize<W: Write>(self, writer: W) -> Result<(), ProgramError> {
1007 if self.account_type == GovernanceAccountType::ProposalV2 {
1008 borsh::to_writer(writer, &self)?
1009 } else if self.account_type == GovernanceAccountType::ProposalV1 {
1010 if self.abstain_vote_weight.is_some() {
1014 panic!("ProposalV1 doesn't support Abstain vote")
1015 }
1016
1017 if self.veto_vote_weight > 0 {
1018 panic!("ProposalV1 doesn't support Veto vote")
1019 }
1020
1021 if self.start_voting_at.is_some() {
1022 panic!("ProposalV1 doesn't support start time")
1023 }
1024
1025 if self.max_voting_time.is_some() {
1026 panic!("ProposalV1 doesn't support max voting time")
1027 }
1028
1029 if self.options.len() != 1 {
1030 panic!("ProposalV1 doesn't support multiple options")
1031 }
1032
1033 let proposal_data_v1 = ProposalV1 {
1034 account_type: self.account_type,
1035 governance: self.governance,
1036 governing_token_mint: self.governing_token_mint,
1037 state: self.state,
1038 token_owner_record: self.token_owner_record,
1039 signatories_count: self.signatories_count,
1040 signatories_signed_off_count: self.signatories_signed_off_count,
1041 yes_votes_count: self.options[0].vote_weight,
1042 no_votes_count: self.deny_vote_weight.unwrap(),
1043 instructions_executed_count: self.options[0].transactions_executed_count,
1044 instructions_count: self.options[0].transactions_count,
1045 instructions_next_index: self.options[0].transactions_next_index,
1046 draft_at: self.draft_at,
1047 signing_off_at: self.signing_off_at,
1048 voting_at: self.voting_at,
1049 voting_at_slot: self.voting_at_slot,
1050 voting_completed_at: self.voting_completed_at,
1051 executing_at: self.executing_at,
1052 closed_at: self.closed_at,
1053 execution_flags: self.execution_flags,
1054 max_vote_weight: self.max_vote_weight,
1055 vote_threshold: self.vote_threshold,
1056 name: self.name,
1057 description_link: self.description_link,
1058 };
1059
1060 borsh::to_writer(writer, &proposal_data_v1)?
1061 }
1062
1063 Ok(())
1064 }
1065}
1066
1067fn get_min_vote_threshold_weight(
1070 vote_threshold: &VoteThreshold,
1071 max_voter_weight: u64,
1072) -> Result<u64, ProgramError> {
1073 let yes_vote_threshold_percentage = match vote_threshold {
1074 VoteThreshold::YesVotePercentage(yes_vote_threshold_percentage) => {
1075 *yes_vote_threshold_percentage
1076 }
1077 _ => {
1078 return Err(GovernanceError::VoteThresholdTypeNotSupported.into());
1079 }
1080 };
1081
1082 let numerator = (yes_vote_threshold_percentage as u128)
1083 .checked_mul(max_voter_weight as u128)
1084 .unwrap();
1085
1086 let mut yes_vote_threshold = numerator.checked_div(100).unwrap();
1087
1088 if yes_vote_threshold.checked_mul(100).unwrap() < numerator {
1089 yes_vote_threshold = yes_vote_threshold.checked_add(1).unwrap();
1090 }
1091
1092 Ok(yes_vote_threshold as u64)
1093}
1094
1095pub fn get_proposal_data(
1097 program_id: &Pubkey,
1098 proposal_info: &AccountInfo,
1099) -> Result<ProposalV2, ProgramError> {
1100 let account_type: GovernanceAccountType = get_account_type(program_id, proposal_info)?;
1101
1102 if account_type == GovernanceAccountType::ProposalV1 {
1104 let proposal_data_v1 = get_account_data::<ProposalV1>(program_id, proposal_info)?;
1105
1106 let vote_result = match proposal_data_v1.state {
1107 ProposalState::Draft
1108 | ProposalState::SigningOff
1109 | ProposalState::Voting
1110 | ProposalState::Cancelled => OptionVoteResult::None,
1111 ProposalState::Succeeded
1112 | ProposalState::Executing
1113 | ProposalState::ExecutingWithErrors
1114 | ProposalState::Completed => OptionVoteResult::Succeeded,
1115 ProposalState::Vetoed | ProposalState::Defeated => OptionVoteResult::None,
1116 };
1117
1118 return Ok(ProposalV2 {
1119 account_type,
1120 governance: proposal_data_v1.governance,
1121 governing_token_mint: proposal_data_v1.governing_token_mint,
1122 state: proposal_data_v1.state,
1123 token_owner_record: proposal_data_v1.token_owner_record,
1124 signatories_count: proposal_data_v1.signatories_count,
1125 signatories_signed_off_count: proposal_data_v1.signatories_signed_off_count,
1126 vote_type: VoteType::SingleChoice,
1127 options: vec![ProposalOption {
1128 label: "Yes".to_string(),
1129 vote_weight: proposal_data_v1.yes_votes_count,
1130 vote_result,
1131 transactions_executed_count: proposal_data_v1.instructions_executed_count,
1132 transactions_count: proposal_data_v1.instructions_count,
1133 transactions_next_index: proposal_data_v1.instructions_next_index,
1134 }],
1135 deny_vote_weight: Some(proposal_data_v1.no_votes_count),
1136 veto_vote_weight: 0,
1137 abstain_vote_weight: None,
1138 start_voting_at: None,
1139 draft_at: proposal_data_v1.draft_at,
1140 signing_off_at: proposal_data_v1.signing_off_at,
1141 voting_at: proposal_data_v1.voting_at,
1142 voting_at_slot: proposal_data_v1.voting_at_slot,
1143 voting_completed_at: proposal_data_v1.voting_completed_at,
1144 executing_at: proposal_data_v1.executing_at,
1145 closed_at: proposal_data_v1.closed_at,
1146 execution_flags: proposal_data_v1.execution_flags,
1147 max_vote_weight: proposal_data_v1.max_vote_weight,
1148 max_voting_time: None,
1149 vote_threshold: proposal_data_v1.vote_threshold,
1150 name: proposal_data_v1.name,
1151 description_link: proposal_data_v1.description_link,
1152 reserved: [0; 64],
1153 reserved1: 0,
1154 });
1155 }
1156
1157 get_account_data::<ProposalV2>(program_id, proposal_info)
1158}
1159
1160pub fn get_proposal_data_for_governance_and_governing_mint(
1163 program_id: &Pubkey,
1164 proposal_info: &AccountInfo,
1165 governance: &Pubkey,
1166 governing_token_mint: &Pubkey,
1167) -> Result<ProposalV2, ProgramError> {
1168 let proposal_data = get_proposal_data_for_governance(program_id, proposal_info, governance)?;
1169
1170 if proposal_data.governing_token_mint != *governing_token_mint {
1171 return Err(GovernanceError::InvalidGoverningMintForProposal.into());
1172 }
1173
1174 Ok(proposal_data)
1175}
1176
1177pub fn get_proposal_data_for_governance(
1179 program_id: &Pubkey,
1180 proposal_info: &AccountInfo,
1181 governance: &Pubkey,
1182) -> Result<ProposalV2, ProgramError> {
1183 let proposal_data = get_proposal_data(program_id, proposal_info)?;
1184
1185 if proposal_data.governance != *governance {
1186 return Err(GovernanceError::InvalidGovernanceForProposal.into());
1187 }
1188
1189 Ok(proposal_data)
1190}
1191
1192pub fn get_proposal_address_seeds<'a>(
1194 governance: &'a Pubkey,
1195 governing_token_mint: &'a Pubkey,
1196 proposal_seed: &'a Pubkey,
1197) -> [&'a [u8]; 4] {
1198 [
1199 PROGRAM_AUTHORITY_SEED,
1200 governance.as_ref(),
1201 governing_token_mint.as_ref(),
1202 proposal_seed.as_ref(),
1203 ]
1204}
1205
1206pub fn get_proposal_address<'a>(
1208 program_id: &Pubkey,
1209 governance: &'a Pubkey,
1210 governing_token_mint: &'a Pubkey,
1211 proposal_seed: &'a Pubkey,
1212) -> Pubkey {
1213 Pubkey::find_program_address(
1214 &get_proposal_address_seeds(governance, governing_token_mint, proposal_seed),
1215 program_id,
1216 )
1217 .0
1218}
1219
1220pub fn assert_valid_proposal_options(
1222 options: &[String],
1223 vote_type: &VoteType,
1224) -> Result<(), ProgramError> {
1225 if options.is_empty() || options.len() > 10 {
1226 return Err(GovernanceError::InvalidProposalOptions.into());
1227 }
1228
1229 if let VoteType::MultiChoice {
1230 choice_type: _,
1231 min_voter_options,
1232 max_voter_options,
1233 max_winning_options,
1234 } = vote_type
1235 {
1236 if options.len() == 1
1237 || *max_voter_options as usize != options.len()
1238 || *max_winning_options as usize != options.len()
1239 || *min_voter_options != 1
1240 {
1241 return Err(GovernanceError::InvalidMultiChoiceProposalParameters.into());
1242 }
1243 }
1244
1245 if options.iter().any(|o| o.is_empty()) {
1249 return Err(GovernanceError::InvalidProposalOptions.into());
1250 }
1251
1252 Ok(())
1253}
1254
1255#[cfg(test)]
1256mod test {
1257 use {
1258 super::*,
1259 crate::state::{
1260 enums::{MintMaxVoterWeightSource, VoteThreshold},
1261 legacy::ProposalV1,
1262 realm::RealmConfig,
1263 vote_record::VoteChoice,
1264 },
1265 proptest::prelude::*,
1266 solana_program::clock::Epoch,
1267 };
1268
1269 fn create_test_proposal() -> ProposalV2 {
1270 ProposalV2 {
1271 account_type: GovernanceAccountType::TokenOwnerRecordV2,
1272 governance: Pubkey::new_unique(),
1273 governing_token_mint: Pubkey::new_unique(),
1274 max_vote_weight: Some(10),
1275 state: ProposalState::Draft,
1276 token_owner_record: Pubkey::new_unique(),
1277 signatories_count: 10,
1278 signatories_signed_off_count: 5,
1279 description_link: "This is my description".to_string(),
1280 name: "This is my name".to_string(),
1281
1282 start_voting_at: Some(0),
1283 draft_at: 10,
1284 signing_off_at: Some(10),
1285
1286 voting_at: Some(10),
1287 voting_at_slot: Some(500),
1288
1289 voting_completed_at: Some(10),
1290 executing_at: Some(10),
1291 closed_at: Some(10),
1292
1293 vote_type: VoteType::SingleChoice,
1294 options: vec![ProposalOption {
1295 label: "yes".to_string(),
1296 vote_weight: 0,
1297 vote_result: OptionVoteResult::None,
1298 transactions_executed_count: 10,
1299 transactions_count: 10,
1300 transactions_next_index: 10,
1301 }],
1302 deny_vote_weight: Some(0),
1303 abstain_vote_weight: Some(0),
1304 veto_vote_weight: 0,
1305
1306 execution_flags: InstructionExecutionFlags::Ordered,
1307
1308 max_voting_time: Some(0),
1309 vote_threshold: Some(VoteThreshold::YesVotePercentage(100)),
1310
1311 reserved: [0; 64],
1312 reserved1: 0,
1313 }
1314 }
1315
1316 fn create_test_multi_option_proposal() -> ProposalV2 {
1317 let mut proposal = create_test_proposal();
1318 proposal.options = vec![
1319 ProposalOption {
1320 label: "option 1".to_string(),
1321 vote_weight: 0,
1322 vote_result: OptionVoteResult::None,
1323 transactions_executed_count: 10,
1324 transactions_count: 10,
1325 transactions_next_index: 10,
1326 },
1327 ProposalOption {
1328 label: "option 2".to_string(),
1329 vote_weight: 0,
1330 vote_result: OptionVoteResult::None,
1331 transactions_executed_count: 10,
1332 transactions_count: 10,
1333 transactions_next_index: 10,
1334 },
1335 ProposalOption {
1336 label: "option 3".to_string(),
1337 vote_weight: 0,
1338 vote_result: OptionVoteResult::None,
1339 transactions_executed_count: 10,
1340 transactions_count: 10,
1341 transactions_next_index: 10,
1342 },
1343 ];
1344
1345 proposal
1346 }
1347
1348 fn create_test_realm() -> RealmV2 {
1349 RealmV2 {
1350 account_type: GovernanceAccountType::RealmV2,
1351 community_mint: Pubkey::new_unique(),
1352 reserved: [0; 6],
1353
1354 authority: Some(Pubkey::new_unique()),
1355 name: "test-realm".to_string(),
1356 config: RealmConfig {
1357 council_mint: Some(Pubkey::new_unique()),
1358 reserved: [0; 6],
1359 legacy1: 0,
1360 legacy2: 0,
1361
1362 community_mint_max_voter_weight_source:
1363 MintMaxVoterWeightSource::FULL_SUPPLY_FRACTION,
1364 min_community_weight_to_create_governance: 10,
1365 },
1366 legacy1: 0,
1367 reserved_v2: [0; 128],
1368 }
1369 }
1370
1371 fn create_test_governance_config() -> GovernanceConfig {
1372 GovernanceConfig {
1373 community_vote_threshold: VoteThreshold::YesVotePercentage(60),
1374 min_community_weight_to_create_proposal: 5,
1375 min_transaction_hold_up_time: 10,
1376 voting_base_time: 5,
1377 community_vote_tipping: VoteTipping::Strict,
1378 council_vote_threshold: VoteThreshold::YesVotePercentage(60),
1379 council_veto_vote_threshold: VoteThreshold::YesVotePercentage(50),
1380 min_council_weight_to_create_proposal: 1,
1381 council_vote_tipping: VoteTipping::Strict,
1382 community_veto_vote_threshold: VoteThreshold::YesVotePercentage(40),
1383 voting_cool_off_time: 0,
1384 deposit_exempt_proposal_count: 0,
1385 }
1386 }
1387
1388 #[test]
1389 fn test_max_size() {
1390 let mut proposal = create_test_proposal();
1391 proposal.vote_type = VoteType::MultiChoice {
1392 choice_type: MultiChoiceType::FullWeight,
1393 min_voter_options: 1,
1394 max_voter_options: 1,
1395 max_winning_options: 1,
1396 };
1397
1398 let size = proposal.try_to_vec().unwrap().len();
1399
1400 assert_eq!(proposal.get_max_size(), Some(size));
1401 }
1402
1403 #[test]
1404 fn test_multi_option_proposal_max_size() {
1405 let mut proposal = create_test_multi_option_proposal();
1406 proposal.vote_type = VoteType::MultiChoice {
1407 choice_type: MultiChoiceType::FullWeight,
1408 min_voter_options: 1,
1409 max_voter_options: 3,
1410 max_winning_options: 3,
1411 };
1412
1413 let size = proposal.try_to_vec().unwrap().len();
1414
1415 assert_eq!(proposal.get_max_size(), Some(size));
1416 }
1417
1418 prop_compose! {
1419 fn vote_results()(governing_token_supply in 1..=u64::MAX)(
1420 governing_token_supply in Just(governing_token_supply),
1421 vote_count in 0..=governing_token_supply,
1422 ) -> (u64, u64) {
1423 (vote_count, governing_token_supply)
1424 }
1425 }
1426
1427 fn editable_signatory_states() -> impl Strategy<Value = ProposalState> {
1428 prop_oneof![Just(ProposalState::Draft)]
1429 }
1430
1431 proptest! {
1432 #[test]
1433 fn test_assert_can_edit_signatories(state in editable_signatory_states()) {
1434
1435 let mut proposal = create_test_proposal();
1436 proposal.state = state;
1437 proposal.assert_can_edit_signatories().unwrap();
1438
1439 }
1440
1441 }
1442
1443 fn none_editable_signatory_states() -> impl Strategy<Value = ProposalState> {
1444 prop_oneof![
1445 Just(ProposalState::Voting),
1446 Just(ProposalState::Succeeded),
1447 Just(ProposalState::Executing),
1448 Just(ProposalState::ExecutingWithErrors),
1449 Just(ProposalState::Completed),
1450 Just(ProposalState::Cancelled),
1451 Just(ProposalState::Defeated),
1452 Just(ProposalState::Vetoed),
1453 Just(ProposalState::SigningOff),
1454 ]
1455 }
1456
1457 proptest! {
1458 #[test]
1459 fn test_assert_can_edit_signatories_with_invalid_state_error(state in none_editable_signatory_states()) {
1460 let mut proposal = create_test_proposal();
1462 proposal.state = state;
1463
1464 let err = proposal.assert_can_edit_signatories().err().unwrap();
1466
1467 assert_eq!(err, GovernanceError::InvalidStateCannotEditSignatories.into());
1469 }
1470
1471 }
1472
1473 fn sign_off_states() -> impl Strategy<Value = ProposalState> {
1474 prop_oneof![Just(ProposalState::SigningOff), Just(ProposalState::Draft),]
1475 }
1476 proptest! {
1477 #[test]
1478 fn test_assert_can_sign_off(state in sign_off_states()) {
1479 let mut proposal = create_test_proposal();
1480 proposal.state = state;
1481 proposal.assert_can_sign_off().unwrap();
1482 }
1483 }
1484
1485 fn none_sign_off_states() -> impl Strategy<Value = ProposalState> {
1486 prop_oneof![
1487 Just(ProposalState::Voting),
1488 Just(ProposalState::Succeeded),
1489 Just(ProposalState::Executing),
1490 Just(ProposalState::ExecutingWithErrors),
1491 Just(ProposalState::Completed),
1492 Just(ProposalState::Cancelled),
1493 Just(ProposalState::Defeated),
1494 Just(ProposalState::Vetoed),
1495 ]
1496 }
1497
1498 proptest! {
1499 #[test]
1500 fn test_assert_can_sign_off_with_state_error(state in none_sign_off_states()) {
1501 let mut proposal = create_test_proposal();
1503 proposal.state = state;
1504
1505 let err = proposal.assert_can_sign_off().err().unwrap();
1507
1508 assert_eq!(err, GovernanceError::InvalidStateCannotSignOff.into());
1510 }
1511 }
1512
1513 fn cancellable_states() -> impl Strategy<Value = ProposalState> {
1514 prop_oneof![
1515 Just(ProposalState::Draft),
1516 Just(ProposalState::SigningOff),
1517 Just(ProposalState::Voting),
1518 ]
1519 }
1520
1521 proptest! {
1522 #[test]
1523 fn test_assert_can_cancel(state in cancellable_states()) {
1524
1525 let mut proposal = create_test_proposal();
1527 let governance_config = create_test_governance_config();
1528
1529 proposal.state = state;
1531
1532 proposal.assert_can_cancel(&governance_config,1).unwrap();
1534
1535 }
1536
1537 }
1538
1539 fn none_cancellable_states() -> impl Strategy<Value = ProposalState> {
1540 prop_oneof![
1541 Just(ProposalState::Succeeded),
1542 Just(ProposalState::Executing),
1543 Just(ProposalState::ExecutingWithErrors),
1544 Just(ProposalState::Completed),
1545 Just(ProposalState::Cancelled),
1546 Just(ProposalState::Defeated),
1547 Just(ProposalState::Vetoed),
1548 ]
1549 }
1550
1551 proptest! {
1552 #[test]
1553 fn test_assert_can_cancel_with_invalid_state_error(state in none_cancellable_states()) {
1554 let mut proposal = create_test_proposal();
1556 proposal.state = state;
1557
1558 let governance_config = create_test_governance_config();
1559
1560 let err = proposal.assert_can_cancel(&governance_config,1).err().unwrap();
1562
1563 assert_eq!(err, GovernanceError::InvalidStateCannotCancelProposal.into());
1565 }
1566
1567 }
1568
1569 #[derive(Clone, Debug)]
1570 pub struct VoteCastTestCase {
1571 #[allow(dead_code)]
1572 name: &'static str,
1573 governing_token_supply: u64,
1574 yes_vote_threshold_percentage: u8,
1575 yes_votes_count: u64,
1576 no_votes_count: u64,
1577 expected_tipped_state: ProposalState,
1578 expected_finalized_state: ProposalState,
1579 }
1580
1581 fn vote_casting_test_cases() -> impl Strategy<Value = VoteCastTestCase> {
1582 prop_oneof![
1583 Just(VoteCastTestCase {
1585 name: "45:10 @40 -- Nays can still outvote Yeahs",
1586 governing_token_supply: 100,
1587 yes_vote_threshold_percentage: 40,
1588 yes_votes_count: 45,
1589 no_votes_count: 10,
1590 expected_tipped_state: ProposalState::Voting,
1591 expected_finalized_state: ProposalState::Succeeded,
1592 }),
1593 Just(VoteCastTestCase {
1594 name: "49:50 @40 -- In best case scenario it can be 50:50 tie and hence Defeated",
1595 governing_token_supply: 100,
1596 yes_vote_threshold_percentage: 40,
1597 yes_votes_count: 49,
1598 no_votes_count: 50,
1599 expected_tipped_state: ProposalState::Defeated,
1600 expected_finalized_state: ProposalState::Defeated,
1601 }),
1602 Just(VoteCastTestCase {
1603 name: "40:40 @40 -- Still can go either way",
1604 governing_token_supply: 100,
1605 yes_vote_threshold_percentage: 40,
1606 yes_votes_count: 40,
1607 no_votes_count: 40,
1608 expected_tipped_state: ProposalState::Voting,
1609 expected_finalized_state: ProposalState::Defeated,
1610 }),
1611 Just(VoteCastTestCase {
1612 name: "45:45 @40 -- Still can go either way",
1613 governing_token_supply: 100,
1614 yes_vote_threshold_percentage: 40,
1615 yes_votes_count: 45,
1616 no_votes_count: 45,
1617 expected_tipped_state: ProposalState::Voting,
1618 expected_finalized_state: ProposalState::Defeated,
1619 }),
1620 Just(VoteCastTestCase {
1621 name: "50:10 @40 -- Nay sayers can still tie up",
1622 governing_token_supply: 100,
1623 yes_vote_threshold_percentage: 40,
1624 yes_votes_count: 50,
1625 no_votes_count: 10,
1626 expected_tipped_state: ProposalState::Voting,
1627 expected_finalized_state: ProposalState::Succeeded,
1628 }),
1629 Just(VoteCastTestCase {
1630 name: "50:50 @40 -- It's a tie and hence Defeated",
1631 governing_token_supply: 100,
1632 yes_vote_threshold_percentage: 40,
1633 yes_votes_count: 50,
1634 no_votes_count: 50,
1635 expected_tipped_state: ProposalState::Defeated,
1636 expected_finalized_state: ProposalState::Defeated,
1637 }),
1638 Just(VoteCastTestCase {
1639 name: "45:51 @ 40 -- Nays won",
1640 governing_token_supply: 100,
1641 yes_vote_threshold_percentage: 40,
1642 yes_votes_count: 45,
1643 no_votes_count: 51,
1644 expected_tipped_state: ProposalState::Defeated,
1645 expected_finalized_state: ProposalState::Defeated,
1646 }),
1647 Just(VoteCastTestCase {
1648 name: "40:55 @ 40 -- Nays won",
1649 governing_token_supply: 100,
1650 yes_vote_threshold_percentage: 40,
1651 yes_votes_count: 40,
1652 no_votes_count: 55,
1653 expected_tipped_state: ProposalState::Defeated,
1654 expected_finalized_state: ProposalState::Defeated,
1655 }),
1656 Just(VoteCastTestCase {
1658 name: "50:10 @50 -- +1 tie breaker required to tip",
1659 governing_token_supply: 100,
1660 yes_vote_threshold_percentage: 50,
1661 yes_votes_count: 50,
1662 no_votes_count: 10,
1663 expected_tipped_state: ProposalState::Voting,
1664 expected_finalized_state: ProposalState::Succeeded,
1665 }),
1666 Just(VoteCastTestCase {
1667 name: "10:50 @50 -- +1 tie breaker vote not possible any longer",
1668 governing_token_supply: 100,
1669 yes_vote_threshold_percentage: 50,
1670 yes_votes_count: 10,
1671 no_votes_count: 50,
1672 expected_tipped_state: ProposalState::Defeated,
1673 expected_finalized_state: ProposalState::Defeated,
1674 }),
1675 Just(VoteCastTestCase {
1676 name: "50:50 @50 -- +1 tie breaker vote not possible any longer",
1677 governing_token_supply: 100,
1678 yes_vote_threshold_percentage: 50,
1679 yes_votes_count: 50,
1680 no_votes_count: 50,
1681 expected_tipped_state: ProposalState::Defeated,
1682 expected_finalized_state: ProposalState::Defeated,
1683 }),
1684 Just(VoteCastTestCase {
1685 name: "51:10 @ 50 -- Nay sayers can't outvote any longer",
1686 governing_token_supply: 100,
1687 yes_vote_threshold_percentage: 50,
1688 yes_votes_count: 51,
1689 no_votes_count: 10,
1690 expected_tipped_state: ProposalState::Succeeded,
1691 expected_finalized_state: ProposalState::Succeeded,
1692 }),
1693 Just(VoteCastTestCase {
1694 name: "10:51 @ 50 -- Nays won",
1695 governing_token_supply: 100,
1696 yes_vote_threshold_percentage: 50,
1697 yes_votes_count: 10,
1698 no_votes_count: 51,
1699 expected_tipped_state: ProposalState::Defeated,
1700 expected_finalized_state: ProposalState::Defeated,
1701 }),
1702 Just(VoteCastTestCase {
1704 name: "10:10 @ 60 -- Can still go either way",
1705 governing_token_supply: 100,
1706 yes_vote_threshold_percentage: 60,
1707 yes_votes_count: 10,
1708 no_votes_count: 10,
1709 expected_tipped_state: ProposalState::Voting,
1710 expected_finalized_state: ProposalState::Defeated,
1711 }),
1712 Just(VoteCastTestCase {
1713 name: "55:10 @ 60 -- Can still go either way",
1714 governing_token_supply: 100,
1715 yes_vote_threshold_percentage: 60,
1716 yes_votes_count: 55,
1717 no_votes_count: 10,
1718 expected_tipped_state: ProposalState::Voting,
1719 expected_finalized_state: ProposalState::Defeated,
1720 }),
1721 Just(VoteCastTestCase {
1722 name: "60:10 @ 60 -- Yeah reached the required threshold",
1723 governing_token_supply: 100,
1724 yes_vote_threshold_percentage: 60,
1725 yes_votes_count: 60,
1726 no_votes_count: 10,
1727 expected_tipped_state: ProposalState::Succeeded,
1728 expected_finalized_state: ProposalState::Succeeded,
1729 }),
1730 Just(VoteCastTestCase {
1731 name: "61:10 @ 60 -- Yeah won",
1732 governing_token_supply: 100,
1733 yes_vote_threshold_percentage: 60,
1734 yes_votes_count: 61,
1735 no_votes_count: 10,
1736 expected_tipped_state: ProposalState::Succeeded,
1737 expected_finalized_state: ProposalState::Succeeded,
1738 }),
1739 Just(VoteCastTestCase {
1740 name: "10:40 @ 60 -- Yeah can still outvote Nay",
1741 governing_token_supply: 100,
1742 yes_vote_threshold_percentage: 60,
1743 yes_votes_count: 10,
1744 no_votes_count: 40,
1745 expected_tipped_state: ProposalState::Voting,
1746 expected_finalized_state: ProposalState::Defeated,
1747 }),
1748 Just(VoteCastTestCase {
1749 name: "60:40 @ 60 -- Yeah won",
1750 governing_token_supply: 100,
1751 yes_vote_threshold_percentage: 60,
1752 yes_votes_count: 60,
1753 no_votes_count: 40,
1754 expected_tipped_state: ProposalState::Succeeded,
1755 expected_finalized_state: ProposalState::Succeeded,
1756 }),
1757 Just(VoteCastTestCase {
1758 name: "10:41 @ 60 -- Aye can't outvote Nay any longer",
1759 governing_token_supply: 100,
1760 yes_vote_threshold_percentage: 60,
1761 yes_votes_count: 10,
1762 no_votes_count: 41,
1763 expected_tipped_state: ProposalState::Defeated,
1764 expected_finalized_state: ProposalState::Defeated,
1765 }),
1766 Just(VoteCastTestCase {
1767 name: "100:0",
1768 governing_token_supply: 100,
1769 yes_vote_threshold_percentage: 100,
1770 yes_votes_count: 100,
1771 no_votes_count: 0,
1772 expected_tipped_state: ProposalState::Succeeded,
1773 expected_finalized_state: ProposalState::Succeeded,
1774 }),
1775 Just(VoteCastTestCase {
1776 name: "0:100",
1777 governing_token_supply: 100,
1778 yes_vote_threshold_percentage: 100,
1779 yes_votes_count: 0,
1780 no_votes_count: 100,
1781 expected_tipped_state: ProposalState::Defeated,
1782 expected_finalized_state: ProposalState::Defeated,
1783 }),
1784 ]
1785 }
1786
1787 proptest! {
1788 #[test]
1789 fn test_try_tip_vote(test_case in vote_casting_test_cases()) {
1790 let mut proposal = create_test_proposal();
1792
1793 proposal.options[0].vote_weight = test_case.yes_votes_count;
1794 proposal.deny_vote_weight = Some(test_case.no_votes_count);
1795
1796 proposal.state = ProposalState::Voting;
1797
1798
1799 let current_timestamp = 15_i64;
1800
1801 let realm = create_test_realm();
1802 let governing_token_mint = proposal.governing_token_mint;
1803 let vote_kind = VoteKind::Electorate;
1804 let vote_tipping = VoteTipping::Strict;
1805
1806 let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,&governing_token_mint, test_case.governing_token_supply,&vote_kind).unwrap();
1807 let vote_threshold = VoteThreshold::YesVotePercentage(test_case.yes_vote_threshold_percentage);
1808
1809
1810
1811 proposal.try_tip_vote(max_voter_weight, &vote_tipping,current_timestamp,&vote_threshold,&vote_kind).unwrap();
1813
1814 assert_eq!(proposal.state,test_case.expected_tipped_state,"CASE: {:?}",test_case);
1816
1817 if test_case.expected_tipped_state != ProposalState::Voting {
1818 assert_eq!(Some(current_timestamp),proposal.voting_completed_at);
1819
1820 }
1821
1822 match proposal.options[0].vote_result {
1823 OptionVoteResult::Succeeded => {
1824 assert_eq!(ProposalState::Succeeded,test_case.expected_tipped_state)
1825 },
1826 OptionVoteResult::Defeated => {
1827 assert_eq!(ProposalState::Defeated,test_case.expected_tipped_state)
1828 },
1829 OptionVoteResult::None => {
1830 assert_eq!(ProposalState::Voting,test_case.expected_tipped_state)
1831 },
1832 };
1833
1834 }
1835
1836 #[test]
1837 fn test_finalize_vote(test_case in vote_casting_test_cases()) {
1838 let mut proposal = create_test_proposal();
1840
1841 proposal.options[0].vote_weight = test_case.yes_votes_count;
1842 proposal.deny_vote_weight = Some(test_case.no_votes_count);
1843
1844 proposal.state = ProposalState::Voting;
1845
1846 let governance_config = create_test_governance_config();
1847
1848 let current_timestamp = 16_i64;
1849
1850 let realm = create_test_realm();
1851 let governing_token_mint = proposal.governing_token_mint;
1852 let vote_kind = VoteKind::Electorate;
1853
1854 let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,&governing_token_mint,test_case.governing_token_supply,&vote_kind).unwrap();
1855 let vote_threshold = VoteThreshold::YesVotePercentage(test_case.yes_vote_threshold_percentage);
1856
1857 proposal.finalize_vote(max_voter_weight, &governance_config,current_timestamp,&vote_threshold).unwrap();
1859
1860 assert_eq!(proposal.state,test_case.expected_finalized_state,"CASE: {:?}",test_case);
1862 assert_eq!(
1863 Some(proposal.voting_max_time_end(&governance_config)),
1864 proposal.voting_completed_at
1865 );
1866
1867 match proposal.options[0].vote_result {
1868 OptionVoteResult::Succeeded => {
1869 assert_eq!(ProposalState::Succeeded,test_case.expected_finalized_state)
1870 },
1871 OptionVoteResult::Defeated => {
1872 assert_eq!(ProposalState::Defeated,test_case.expected_finalized_state)
1873 },
1874 OptionVoteResult::None => {
1875 panic!("Option result must be resolved for finalized vote")
1876 },
1877 };
1878
1879 }
1880 }
1881
1882 prop_compose! {
1883 fn full_vote_results()(governing_token_supply in 1..=u64::MAX, yes_vote_threshold in 1..100)(
1884 governing_token_supply in Just(governing_token_supply),
1885 yes_vote_threshold in Just(yes_vote_threshold),
1886
1887 yes_votes_count in 0..=governing_token_supply,
1888 no_votes_count in 0..=governing_token_supply,
1889
1890 ) -> (u64, u64, u64, u8) {
1891 (yes_votes_count, no_votes_count, governing_token_supply, yes_vote_threshold as u8)
1892 }
1893 }
1894
1895 proptest! {
1896 #[test]
1897 fn test_try_tip_vote_with_full_vote_results(
1898 (yes_votes_count, no_votes_count, governing_token_supply, yes_vote_threshold_percentage) in full_vote_results(),
1899
1900 ) {
1901 let mut proposal = create_test_proposal();
1904
1905 proposal.options[0].vote_weight = yes_votes_count;
1906 proposal.deny_vote_weight = Some(no_votes_count.min(governing_token_supply-yes_votes_count));
1907
1908
1909 proposal.state = ProposalState::Voting;
1910
1911
1912
1913 let yes_vote_threshold_percentage = VoteThreshold::YesVotePercentage(yes_vote_threshold_percentage);
1914
1915 let current_timestamp = 15_i64;
1916
1917 let realm = create_test_realm();
1918 let governing_token_mint = proposal.governing_token_mint;
1919 let vote_kind = VoteKind::Electorate;
1920 let vote_tipping = VoteTipping::Strict;
1921
1922 let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,&governing_token_mint,governing_token_supply,&vote_kind).unwrap();
1923
1924 proposal.try_tip_vote(max_voter_weight, &vote_tipping, current_timestamp,&yes_vote_threshold_percentage,&vote_kind).unwrap();
1926
1927 let yes_vote_threshold_count = get_min_vote_threshold_weight(&yes_vote_threshold_percentage,governing_token_supply).unwrap();
1929
1930 let no_vote_weight = proposal.deny_vote_weight.unwrap();
1931
1932 if yes_votes_count >= yes_vote_threshold_count && yes_votes_count > (governing_token_supply - yes_votes_count)
1933 {
1934 assert_eq!(proposal.state,ProposalState::Succeeded);
1935 } else if no_vote_weight > (governing_token_supply - yes_vote_threshold_count)
1936 || no_vote_weight >= (governing_token_supply - no_vote_weight ) {
1937 assert_eq!(proposal.state,ProposalState::Defeated);
1938 } else {
1939 assert_eq!(proposal.state,ProposalState::Voting);
1940 }
1941 }
1942 }
1943
1944 proptest! {
1945 #[test]
1946 fn test_finalize_vote_with_full_vote_results(
1947 (yes_votes_count, no_votes_count, governing_token_supply, yes_vote_threshold_percentage) in full_vote_results(),
1948
1949 ) {
1950 let mut proposal = create_test_proposal();
1952
1953 proposal.options[0].vote_weight = yes_votes_count;
1954 proposal.deny_vote_weight = Some(no_votes_count.min(governing_token_supply-yes_votes_count));
1955
1956 proposal.state = ProposalState::Voting;
1957
1958
1959 let governance_config = create_test_governance_config();
1960 let yes_vote_threshold_percentage = VoteThreshold::YesVotePercentage(yes_vote_threshold_percentage);
1961
1962
1963 let current_timestamp = 16_i64;
1964
1965 let realm = create_test_realm();
1966 let governing_token_mint = proposal.governing_token_mint;
1967 let vote_kind = VoteKind::Electorate;
1968
1969 let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,&governing_token_mint,governing_token_supply,&vote_kind).unwrap();
1970
1971 proposal.finalize_vote(max_voter_weight, &governance_config,current_timestamp, &yes_vote_threshold_percentage).unwrap();
1973
1974 let no_vote_weight = proposal.deny_vote_weight.unwrap();
1976
1977 let yes_vote_threshold_count = get_min_vote_threshold_weight(&yes_vote_threshold_percentage,governing_token_supply).unwrap();
1978
1979 if yes_votes_count >= yes_vote_threshold_count && yes_votes_count > no_vote_weight
1980 {
1981 assert_eq!(proposal.state,ProposalState::Succeeded);
1982 } else {
1983 assert_eq!(proposal.state,ProposalState::Defeated);
1984 }
1985 }
1986 }
1987
1988 #[test]
1989 fn test_try_tip_vote_with_reduced_community_mint_max_vote_weight() {
1990 let mut proposal = create_test_proposal();
1992
1993 proposal.options[0].vote_weight = 60;
1994 proposal.deny_vote_weight = Some(10);
1995
1996 proposal.state = ProposalState::Voting;
1997
1998 let current_timestamp = 15_i64;
1999
2000 let community_token_supply = 200;
2001
2002 let mut realm = create_test_realm();
2003 let governing_token_mint = proposal.governing_token_mint;
2004 let vote_kind = VoteKind::Electorate;
2005 let vote_tipping = VoteTipping::Strict;
2006
2007 realm.config.community_mint_max_voter_weight_source =
2009 MintMaxVoterWeightSource::SupplyFraction(
2010 MintMaxVoterWeightSource::SUPPLY_FRACTION_BASE / 2,
2011 );
2012
2013 let max_voter_weight = proposal
2014 .get_max_voter_weight_from_mint_supply(
2015 &realm,
2016 &governing_token_mint,
2017 community_token_supply,
2018 &vote_kind,
2019 )
2020 .unwrap();
2021
2022 let vote_threshold = &VoteThreshold::YesVotePercentage(60);
2023 let vote_kind = VoteKind::Electorate;
2024
2025 proposal
2027 .try_tip_vote(
2028 max_voter_weight,
2029 &vote_tipping,
2030 current_timestamp,
2031 vote_threshold,
2032 &vote_kind,
2033 )
2034 .unwrap();
2035
2036 assert_eq!(proposal.state, ProposalState::Succeeded);
2038 assert_eq!(proposal.max_vote_weight, Some(100));
2039 }
2040
2041 #[test]
2042 fn test_try_tip_vote_with_reduced_absolute_community_mint_max_vote_weight() {
2043 let mut proposal = create_test_proposal();
2045
2046 proposal.options[0].vote_weight = 60;
2047 proposal.deny_vote_weight = Some(10);
2048
2049 proposal.state = ProposalState::Voting;
2050
2051 let current_timestamp = 15_i64;
2052
2053 let community_token_supply = 200;
2054
2055 let mut realm = create_test_realm();
2056 let governing_token_mint = proposal.governing_token_mint;
2057 let vote_kind = VoteKind::Electorate;
2058 let vote_tipping = VoteTipping::Strict;
2059
2060 realm.config.community_mint_max_voter_weight_source =
2062 MintMaxVoterWeightSource::Absolute(community_token_supply / 2);
2063
2064 let max_voter_weight = proposal
2065 .get_max_voter_weight_from_mint_supply(
2066 &realm,
2067 &governing_token_mint,
2068 community_token_supply,
2069 &vote_kind,
2070 )
2071 .unwrap();
2072
2073 let vote_threshold = &VoteThreshold::YesVotePercentage(60);
2074 let vote_kind = VoteKind::Electorate;
2075
2076 proposal
2078 .try_tip_vote(
2079 max_voter_weight,
2080 &vote_tipping,
2081 current_timestamp,
2082 vote_threshold,
2083 &vote_kind,
2084 )
2085 .unwrap();
2086
2087 assert_eq!(proposal.state, ProposalState::Succeeded);
2089 assert_eq!(proposal.max_vote_weight, Some(100));
2090 }
2091
2092 #[test]
2093 fn test_try_tip_vote_with_reduced_community_mint_max_vote_weight_and_vote_overflow() {
2094 let mut proposal = create_test_proposal();
2096
2097 proposal.deny_vote_weight = Some(10);
2099
2100 proposal.state = ProposalState::Voting;
2101
2102 let current_timestamp = 15_i64;
2103
2104 let community_token_supply = 200;
2105
2106 let mut realm = create_test_realm();
2107 let governing_token_mint = proposal.governing_token_mint;
2108 let vote_kind = VoteKind::Electorate;
2109 let vote_tipping = VoteTipping::Strict;
2110
2111 realm.config.community_mint_max_voter_weight_source =
2113 MintMaxVoterWeightSource::SupplyFraction(
2114 MintMaxVoterWeightSource::SUPPLY_FRACTION_BASE / 2,
2115 );
2116
2117 proposal.options[0].vote_weight = 120;
2120
2121 let max_voter_weight = proposal
2122 .get_max_voter_weight_from_mint_supply(
2123 &realm,
2124 &governing_token_mint,
2125 community_token_supply,
2126 &vote_kind,
2127 )
2128 .unwrap();
2129
2130 let vote_threshold = VoteThreshold::YesVotePercentage(60);
2131
2132 proposal
2134 .try_tip_vote(
2135 max_voter_weight,
2136 &vote_tipping,
2137 current_timestamp,
2138 &vote_threshold,
2139 &vote_kind,
2140 )
2141 .unwrap();
2142
2143 assert_eq!(proposal.state, ProposalState::Succeeded);
2145 assert_eq!(proposal.max_vote_weight, Some(130));
2146 }
2147
2148 #[test]
2149 fn test_try_tip_vote_with_reduced_absolute_mint_max_vote_weight_and_vote_overflow() {
2150 let mut proposal = create_test_proposal();
2152
2153 proposal.deny_vote_weight = Some(10);
2155
2156 proposal.state = ProposalState::Voting;
2157
2158 let current_timestamp = 15_i64;
2159
2160 let community_token_supply = 200;
2161
2162 let mut realm = create_test_realm();
2163 let governing_token_mint = proposal.governing_token_mint;
2164 let vote_kind = VoteKind::Electorate;
2165 let vote_tipping = VoteTipping::Strict;
2166
2167 realm.config.community_mint_max_voter_weight_source =
2169 MintMaxVoterWeightSource::Absolute(community_token_supply / 2);
2170
2171 proposal.options[0].vote_weight = 120;
2174
2175 let max_voter_weight = proposal
2176 .get_max_voter_weight_from_mint_supply(
2177 &realm,
2178 &governing_token_mint,
2179 community_token_supply,
2180 &vote_kind,
2181 )
2182 .unwrap();
2183
2184 let vote_threshold = VoteThreshold::YesVotePercentage(60);
2185
2186 proposal
2188 .try_tip_vote(
2189 max_voter_weight,
2190 &vote_tipping,
2191 current_timestamp,
2192 &vote_threshold,
2193 &vote_kind,
2194 )
2195 .unwrap();
2196
2197 assert_eq!(proposal.state, ProposalState::Succeeded);
2199 assert_eq!(proposal.max_vote_weight, Some(130)); }
2202
2203 #[test]
2204 fn test_try_tip_vote_for_council_vote_with_reduced_community_mint_max_vote_weight() {
2205 let mut proposal = create_test_proposal();
2207
2208 proposal.options[0].vote_weight = 60;
2209 proposal.deny_vote_weight = Some(10);
2210
2211 proposal.state = ProposalState::Voting;
2212
2213 let current_timestamp = 15_i64;
2214
2215 let community_token_supply = 200;
2216
2217 let mut realm = create_test_realm();
2218 let governing_token_mint = proposal.governing_token_mint;
2219 let vote_kind = VoteKind::Electorate;
2220 let vote_tipping = VoteTipping::Strict;
2221
2222 realm.config.community_mint_max_voter_weight_source =
2223 MintMaxVoterWeightSource::SupplyFraction(
2224 MintMaxVoterWeightSource::SUPPLY_FRACTION_BASE / 2,
2225 );
2226 realm.config.council_mint = Some(proposal.governing_token_mint);
2227
2228 let max_voter_weight = proposal
2229 .get_max_voter_weight_from_mint_supply(
2230 &realm,
2231 &governing_token_mint,
2232 community_token_supply,
2233 &vote_kind,
2234 )
2235 .unwrap();
2236
2237 let vote_threshold = VoteThreshold::YesVotePercentage(60);
2238
2239 proposal
2241 .try_tip_vote(
2242 max_voter_weight,
2243 &vote_tipping,
2244 current_timestamp,
2245 &vote_threshold,
2246 &vote_kind,
2247 )
2248 .unwrap();
2249
2250 assert_eq!(proposal.state, ProposalState::Voting);
2252 }
2253
2254 #[test]
2255 fn test_finalize_vote_with_reduced_community_mint_max_vote_weight() {
2256 let mut proposal = create_test_proposal();
2258
2259 proposal.options[0].vote_weight = 60;
2260 proposal.deny_vote_weight = Some(10);
2261
2262 proposal.state = ProposalState::Voting;
2263
2264 let governance_config = create_test_governance_config();
2265
2266 let current_timestamp = 16_i64;
2267 let community_token_supply = 200;
2268
2269 let mut realm = create_test_realm();
2270 let governing_token_mint = proposal.governing_token_mint;
2271 let vote_kind = VoteKind::Electorate;
2272
2273 realm.config.community_mint_max_voter_weight_source =
2275 MintMaxVoterWeightSource::SupplyFraction(
2276 MintMaxVoterWeightSource::SUPPLY_FRACTION_BASE / 2,
2277 );
2278
2279 let max_voter_weight = proposal
2280 .get_max_voter_weight_from_mint_supply(
2281 &realm,
2282 &governing_token_mint,
2283 community_token_supply,
2284 &vote_kind,
2285 )
2286 .unwrap();
2287
2288 let vote_threshold = VoteThreshold::YesVotePercentage(60);
2289
2290 proposal
2292 .finalize_vote(
2293 max_voter_weight,
2294 &governance_config,
2295 current_timestamp,
2296 &vote_threshold,
2297 )
2298 .unwrap();
2299
2300 assert_eq!(proposal.state, ProposalState::Succeeded);
2302 assert_eq!(proposal.max_vote_weight, Some(100));
2303 }
2304
2305 #[test]
2306 fn test_finalize_vote_with_reduced_community_mint_max_vote_weight_and_vote_overflow() {
2307 let mut proposal = create_test_proposal();
2309
2310 proposal.options[0].vote_weight = 60;
2311 proposal.deny_vote_weight = Some(10);
2312
2313 proposal.state = ProposalState::Voting;
2314
2315 let governance_config = create_test_governance_config();
2316
2317 let current_timestamp = 16_i64;
2318 let community_token_supply = 200;
2319
2320 let mut realm = create_test_realm();
2321 let governing_token_mint = proposal.governing_token_mint;
2322 let vote_kind = VoteKind::Electorate;
2323
2324 realm.config.community_mint_max_voter_weight_source =
2326 MintMaxVoterWeightSource::SupplyFraction(
2327 MintMaxVoterWeightSource::SUPPLY_FRACTION_BASE / 2,
2328 );
2329
2330 proposal.options[0].vote_weight = 120;
2332
2333 let max_voter_weight = proposal
2334 .get_max_voter_weight_from_mint_supply(
2335 &realm,
2336 &governing_token_mint,
2337 community_token_supply,
2338 &vote_kind,
2339 )
2340 .unwrap();
2341
2342 let vote_threshold = VoteThreshold::YesVotePercentage(60);
2343
2344 proposal
2346 .finalize_vote(
2347 max_voter_weight,
2348 &governance_config,
2349 current_timestamp,
2350 &vote_threshold,
2351 )
2352 .unwrap();
2353
2354 assert_eq!(proposal.state, ProposalState::Succeeded);
2356 assert_eq!(proposal.max_vote_weight, Some(130));
2357 }
2358
2359 #[test]
2360 pub fn test_finalize_vote_with_expired_voting_time_error() {
2361 let mut proposal = create_test_proposal();
2363 proposal.state = ProposalState::Voting;
2364 let governance_config = create_test_governance_config();
2365
2366 let current_timestamp =
2367 proposal.voting_at.unwrap() + governance_config.voting_base_time as i64;
2368
2369 let realm = create_test_realm();
2370 let governing_token_mint = proposal.governing_token_mint;
2371 let vote_kind = VoteKind::Electorate;
2372
2373 let max_voter_weight = proposal
2374 .get_max_voter_weight_from_mint_supply(&realm, &governing_token_mint, 100, &vote_kind)
2375 .unwrap();
2376
2377 let vote_threshold = &governance_config.community_vote_threshold;
2378
2379 let err = proposal
2381 .finalize_vote(
2382 max_voter_weight,
2383 &governance_config,
2384 current_timestamp,
2385 vote_threshold,
2386 )
2387 .err()
2388 .unwrap();
2389
2390 assert_eq!(err, GovernanceError::CannotFinalizeVotingInProgress.into());
2392 }
2393
2394 #[test]
2395 pub fn test_finalize_vote_after_voting_time() {
2396 let mut proposal = create_test_proposal();
2398 proposal.state = ProposalState::Voting;
2399 let governance_config = create_test_governance_config();
2400
2401 let current_timestamp =
2402 proposal.voting_at.unwrap() + governance_config.voting_base_time as i64 + 1;
2403
2404 let realm = create_test_realm();
2405 let governing_token_mint = proposal.governing_token_mint;
2406 let vote_kind = VoteKind::Electorate;
2407
2408 let max_voter_weight = proposal
2409 .get_max_voter_weight_from_mint_supply(&realm, &governing_token_mint, 100, &vote_kind)
2410 .unwrap();
2411
2412 let vote_threshold = &governance_config.community_vote_threshold;
2413
2414 let result = proposal.finalize_vote(
2416 max_voter_weight,
2417 &governance_config,
2418 current_timestamp,
2419 vote_threshold,
2420 );
2421
2422 assert_eq!(result, Ok(()));
2424 }
2425
2426 #[test]
2427 pub fn test_assert_can_vote_with_expired_voting_time_error() {
2428 let mut proposal = create_test_proposal();
2430 proposal.state = ProposalState::Voting;
2431 let governance_config = create_test_governance_config();
2432
2433 let current_timestamp =
2434 proposal.voting_at.unwrap() + governance_config.voting_base_time as i64 + 1;
2435
2436 let vote = Vote::Approve(vec![]);
2437
2438 let err = proposal
2440 .assert_can_cast_vote(&governance_config, &vote, current_timestamp)
2441 .err()
2442 .unwrap();
2443
2444 assert_eq!(err, GovernanceError::ProposalVotingTimeExpired.into());
2446 }
2447
2448 #[test]
2449 pub fn test_assert_can_vote_within_voting_time() {
2450 let mut proposal = create_test_proposal();
2452 proposal.state = ProposalState::Voting;
2453 let governance_config = create_test_governance_config();
2454
2455 let current_timestamp =
2456 proposal.voting_at.unwrap() + governance_config.voting_base_time as i64;
2457
2458 let vote = Vote::Approve(vec![]);
2459
2460 let result = proposal.assert_can_cast_vote(&governance_config, &vote, current_timestamp);
2462
2463 assert_eq!(result, Ok(()));
2465 }
2466
2467 #[test]
2468 pub fn test_assert_can_vote_approve_before_voting_cool_off_time() {
2469 let mut proposal = create_test_proposal();
2471 proposal.state = ProposalState::Voting;
2472
2473 let mut governance_config = create_test_governance_config();
2474 governance_config.voting_cool_off_time = 2;
2475
2476 let current_timestamp =
2477 proposal.voting_at.unwrap() + governance_config.voting_base_time as i64 - 1;
2478
2479 let vote = Vote::Approve(vec![]);
2480
2481 let result = proposal.assert_can_cast_vote(&governance_config, &vote, current_timestamp);
2483
2484 assert_eq!(result, Ok(()));
2486 }
2487
2488 #[test]
2489 pub fn test_assert_cannot_vote_approve_within_voting_cool_off_time() {
2490 let mut proposal = create_test_proposal();
2492 proposal.state = ProposalState::Voting;
2493
2494 let mut governance_config = create_test_governance_config();
2495 governance_config.voting_cool_off_time = 2;
2496
2497 let current_timestamp =
2498 proposal.voting_at.unwrap() + governance_config.voting_base_time as i64 + 1;
2499
2500 let vote = Vote::Approve(vec![]);
2501
2502 let err = proposal
2504 .assert_can_cast_vote(&governance_config, &vote, current_timestamp)
2505 .err()
2506 .unwrap();
2507
2508 assert_eq!(err, GovernanceError::VoteNotAllowedInCoolOffTime.into());
2510 }
2511
2512 #[test]
2513 pub fn test_assert_can_vote_veto_within_voting_cool_off_time() {
2514 let mut proposal = create_test_proposal();
2516 proposal.state = ProposalState::Voting;
2517
2518 let mut governance_config = create_test_governance_config();
2519 governance_config.voting_cool_off_time = 2;
2520
2521 let current_timestamp =
2522 proposal.voting_at.unwrap() + governance_config.voting_base_time as i64 + 1;
2523
2524 let vote = Vote::Veto;
2525
2526 let result = proposal.assert_can_cast_vote(&governance_config, &vote, current_timestamp);
2528
2529 assert_eq!(result, Ok(()));
2531 }
2532
2533 #[test]
2534 pub fn test_assert_can_vote_deny_within_voting_cool_off_time() {
2535 let mut proposal = create_test_proposal();
2537 proposal.state = ProposalState::Voting;
2538
2539 let mut governance_config = create_test_governance_config();
2540 governance_config.voting_cool_off_time = 1;
2541
2542 let current_timestamp =
2543 proposal.voting_at.unwrap() + governance_config.voting_base_time as i64 + 1;
2544
2545 let vote = Vote::Deny;
2546
2547 let result = proposal.assert_can_cast_vote(&governance_config, &vote, current_timestamp);
2549
2550 assert_eq!(result, Ok(()));
2552 }
2553
2554 #[test]
2555 pub fn test_assert_valid_vote_with_deny_vote_for_survey_only_proposal_error() {
2556 let mut proposal = create_test_proposal();
2558 proposal.deny_vote_weight = None;
2559
2560 let vote = Vote::Deny;
2562
2563 let result = proposal.assert_valid_vote(&vote);
2565
2566 assert_eq!(result, Err(GovernanceError::DenyVoteIsNotAllowed.into()));
2568 }
2569
2570 #[test]
2571 pub fn test_assert_valid_vote_with_too_many_options_error() {
2572 let proposal = create_test_proposal();
2574
2575 let choices = vec![
2576 VoteChoice {
2577 rank: 0,
2578 weight_percentage: 100,
2579 },
2580 VoteChoice {
2581 rank: 0,
2582 weight_percentage: 100,
2583 },
2584 ];
2585
2586 let vote = Vote::Approve(choices.clone());
2587
2588 assert!(proposal.options.len() != choices.len());
2590
2591 let result = proposal.assert_valid_vote(&vote);
2593
2594 assert_eq!(
2596 result,
2597 Err(GovernanceError::InvalidNumberOfVoteChoices.into())
2598 );
2599 }
2600
2601 #[test]
2602 pub fn test_assert_valid_vote_with_no_choice_for_single_choice_error() {
2603 let proposal = create_test_proposal();
2605
2606 let choices = vec![VoteChoice {
2607 rank: 0,
2608 weight_percentage: 0,
2609 }];
2610
2611 let vote = Vote::Approve(choices.clone());
2612
2613 assert_eq!(proposal.options.len(), choices.len());
2615
2616 let result = proposal.assert_valid_vote(&vote);
2618
2619 assert_eq!(
2621 result,
2622 Err(GovernanceError::SingleChoiceOnlyIsAllowed.into())
2623 );
2624 }
2625
2626 #[test]
2627 pub fn test_assert_valid_vote_with_to_many_choices_for_single_choice_error() {
2628 let proposal = create_test_multi_option_proposal();
2630 let choices = vec![
2631 VoteChoice {
2632 rank: 0,
2633 weight_percentage: 100,
2634 },
2635 VoteChoice {
2636 rank: 0,
2637 weight_percentage: 100,
2638 },
2639 VoteChoice {
2640 rank: 0,
2641 weight_percentage: 0,
2642 },
2643 ];
2644
2645 let vote = Vote::Approve(choices.clone());
2646
2647 assert_eq!(proposal.options.len(), choices.len());
2649
2650 let result = proposal.assert_valid_vote(&vote);
2652
2653 assert_eq!(
2655 result,
2656 Err(GovernanceError::SingleChoiceOnlyIsAllowed.into())
2657 );
2658 }
2659
2660 #[test]
2661 pub fn test_assert_valid_multi_choice_full_weight_vote() {
2662 let mut proposal = create_test_multi_option_proposal();
2664 proposal.vote_type = VoteType::MultiChoice {
2665 choice_type: MultiChoiceType::FullWeight,
2666 min_voter_options: 1,
2667 max_voter_options: 4,
2668 max_winning_options: 4,
2669 };
2670 let choices = vec![
2671 VoteChoice {
2672 rank: 0,
2673 weight_percentage: 100,
2674 },
2675 VoteChoice {
2676 rank: 0,
2677 weight_percentage: 100,
2678 },
2679 VoteChoice {
2680 rank: 0,
2681 weight_percentage: 100,
2682 },
2683 ];
2684
2685 let vote = Vote::Approve(choices.clone());
2686
2687 assert_eq!(proposal.options.len(), choices.len());
2689
2690 let result = proposal.assert_valid_vote(&vote);
2692
2693 assert_eq!(result, Ok(()));
2695 }
2696
2697 #[test]
2698 pub fn test_assert_valid_vote_with_no_choices_for_multi_choice_error() {
2699 let mut proposal = create_test_multi_option_proposal();
2701 proposal.vote_type = VoteType::MultiChoice {
2702 choice_type: MultiChoiceType::FullWeight,
2703 min_voter_options: 1,
2704 max_voter_options: 3,
2705 max_winning_options: 3,
2706 };
2707
2708 let choices = vec![
2709 VoteChoice {
2710 rank: 0,
2711 weight_percentage: 0,
2712 },
2713 VoteChoice {
2714 rank: 0,
2715 weight_percentage: 0,
2716 },
2717 VoteChoice {
2718 rank: 0,
2719 weight_percentage: 0,
2720 },
2721 ];
2722
2723 let vote = Vote::Approve(choices.clone());
2724
2725 assert_eq!(proposal.options.len(), choices.len());
2727
2728 let result = proposal.assert_valid_vote(&vote);
2730
2731 assert_eq!(
2733 result,
2734 Err(GovernanceError::AtLeastSingleChoiceIsRequired.into())
2735 );
2736 }
2737
2738 #[test]
2739 pub fn test_assert_valid_vote_with_choice_weight_not_100_percent_error() {
2740 let mut proposal = create_test_multi_option_proposal();
2742 proposal.vote_type = VoteType::MultiChoice {
2743 choice_type: MultiChoiceType::FullWeight,
2744 min_voter_options: 1,
2745 max_voter_options: 3,
2746 max_winning_options: 3,
2747 };
2748
2749 let choices = vec![
2750 VoteChoice {
2751 rank: 0,
2752 weight_percentage: 50,
2753 },
2754 VoteChoice {
2755 rank: 0,
2756 weight_percentage: 50,
2757 },
2758 VoteChoice {
2759 rank: 0,
2760 weight_percentage: 0,
2761 },
2762 ];
2763
2764 let vote = Vote::Approve(choices.clone());
2765
2766 assert_eq!(proposal.options.len(), choices.len());
2768
2769 let result = proposal.assert_valid_vote(&vote);
2771
2772 assert_eq!(
2774 result,
2775 Err(GovernanceError::ChoiceWeightMustBe100Percent.into())
2776 );
2777 }
2778
2779 #[test]
2780 pub fn test_assert_valid_proposal_options_with_invalid_choice_number_for_multi_choice_vote_error(
2781 ) {
2782 let vote_type = VoteType::MultiChoice {
2784 choice_type: MultiChoiceType::FullWeight,
2785 min_voter_options: 1,
2786 max_voter_options: 3,
2787 max_winning_options: 3,
2788 };
2789
2790 let options = vec!["option 1".to_string(), "option 2".to_string()];
2791
2792 let result = assert_valid_proposal_options(&options, &vote_type);
2794
2795 assert_eq!(
2797 result,
2798 Err(GovernanceError::InvalidMultiChoiceProposalParameters.into())
2799 );
2800 }
2801
2802 #[test]
2803 pub fn test_assert_valid_proposal_options_with_no_options_for_multi_choice_vote_error() {
2804 let vote_type = VoteType::MultiChoice {
2806 choice_type: MultiChoiceType::FullWeight,
2807 min_voter_options: 1,
2808 max_voter_options: 3,
2809 max_winning_options: 3,
2810 };
2811
2812 let options = vec![];
2813
2814 let result = assert_valid_proposal_options(&options, &vote_type);
2816
2817 assert_eq!(result, Err(GovernanceError::InvalidProposalOptions.into()));
2819 }
2820
2821 #[test]
2822 pub fn test_assert_valid_proposal_options_with_no_options_for_single_choice_vote_error() {
2823 let vote_type = VoteType::SingleChoice;
2825
2826 let options = vec![];
2827
2828 let result = assert_valid_proposal_options(&options, &vote_type);
2830
2831 assert_eq!(result, Err(GovernanceError::InvalidProposalOptions.into()));
2833 }
2834
2835 #[test]
2836 pub fn test_assert_valid_proposal_options_for_multi_choice_vote() {
2837 let vote_type = VoteType::MultiChoice {
2839 choice_type: MultiChoiceType::FullWeight,
2840 min_voter_options: 1,
2841 max_voter_options: 3,
2842 max_winning_options: 3,
2843 };
2844
2845 let options = vec![
2846 "option 1".to_string(),
2847 "option 2".to_string(),
2848 "option 3".to_string(),
2849 ];
2850
2851 let result = assert_valid_proposal_options(&options, &vote_type);
2853
2854 assert_eq!(result, Ok(()));
2856 }
2857
2858 #[test]
2859 pub fn test_assert_valid_proposal_options_for_multi_choice_vote_with_empty_option_error() {
2860 let vote_type = VoteType::MultiChoice {
2862 choice_type: MultiChoiceType::FullWeight,
2863 min_voter_options: 1,
2864 max_voter_options: 3,
2865 max_winning_options: 3,
2866 };
2867
2868 let options = vec![
2869 "".to_string(),
2870 "option 2".to_string(),
2871 "option 3".to_string(),
2872 ];
2873
2874 let result = assert_valid_proposal_options(&options, &vote_type);
2876
2877 assert_eq!(result, Err(GovernanceError::InvalidProposalOptions.into()));
2879 }
2880
2881 #[test]
2882 pub fn test_assert_valid_vote_for_multi_weighted_choice() {
2883 let mut proposal = create_test_multi_option_proposal();
2886 proposal.vote_type = VoteType::MultiChoice {
2887 choice_type: MultiChoiceType::Weighted,
2888 min_voter_options: 1,
2889 max_voter_options: 3,
2890 max_winning_options: 3,
2891 };
2892
2893 let choices = vec![
2894 VoteChoice {
2895 rank: 0,
2896 weight_percentage: 42,
2897 },
2898 VoteChoice {
2899 rank: 0,
2900 weight_percentage: 42,
2901 },
2902 VoteChoice {
2903 rank: 0,
2904 weight_percentage: 16,
2905 },
2906 ];
2907 let vote = Vote::Approve(choices.clone());
2908
2909 assert_eq!(proposal.options.len(), choices.len());
2911
2912 let result = proposal.assert_valid_vote(&vote);
2914
2915 assert_eq!(result, Ok(()));
2917 }
2918
2919 #[test]
2920 pub fn test_assert_valid_full_vote_for_multi_weighted_choice() {
2921 let mut proposal = create_test_multi_option_proposal();
2924 proposal.vote_type = VoteType::MultiChoice {
2925 choice_type: MultiChoiceType::Weighted,
2926 min_voter_options: 1,
2927 max_voter_options: 3,
2928 max_winning_options: 3,
2929 };
2930
2931 let choices = vec![
2932 VoteChoice {
2933 rank: 0,
2934 weight_percentage: 0,
2935 },
2936 VoteChoice {
2937 rank: 0,
2938 weight_percentage: 100,
2939 },
2940 VoteChoice {
2941 rank: 0,
2942 weight_percentage: 0,
2943 },
2944 ];
2945 let vote = Vote::Approve(choices.clone());
2946
2947 assert_eq!(proposal.options.len(), choices.len());
2949
2950 let result = proposal.assert_valid_vote(&vote);
2952
2953 assert_eq!(result, Ok(()));
2955 }
2956
2957 #[test]
2958 pub fn test_assert_valid_vote_with_total_vote_weight_above_100_percent_for_multi_weighted_choice_error(
2959 ) {
2960 let mut proposal = create_test_multi_option_proposal();
2962 proposal.vote_type = VoteType::MultiChoice {
2963 choice_type: MultiChoiceType::Weighted,
2964 min_voter_options: 1,
2965 max_voter_options: 2,
2966 max_winning_options: 2,
2967 };
2968
2969 let choices = vec![
2970 VoteChoice {
2971 rank: 0,
2972 weight_percentage: 34,
2973 },
2974 VoteChoice {
2975 rank: 0,
2976 weight_percentage: 34,
2977 },
2978 VoteChoice {
2979 rank: 0,
2980 weight_percentage: 34,
2981 },
2982 ];
2983 let vote = Vote::Approve(choices.clone());
2984
2985 assert_eq!(proposal.options.len(), choices.len());
2987
2988 let result = proposal.assert_valid_vote(&vote);
2990
2991 assert_eq!(
2993 result,
2994 Err(GovernanceError::TotalVoteWeightMustBe100Percent.into())
2995 );
2996 }
2997
2998 #[test]
2999 pub fn test_assert_valid_vote_with_over_percentage_for_multi_weighted_choice_error() {
3000 let mut proposal = create_test_multi_option_proposal();
3003 proposal.vote_type = VoteType::MultiChoice {
3004 choice_type: MultiChoiceType::Weighted,
3005 min_voter_options: 1,
3006 max_voter_options: 3,
3007 max_winning_options: 3,
3008 };
3009
3010 let choices = vec![
3011 VoteChoice {
3012 rank: 0,
3013 weight_percentage: 34,
3014 },
3015 VoteChoice {
3016 rank: 0,
3017 weight_percentage: 34,
3018 },
3019 VoteChoice {
3020 rank: 0,
3021 weight_percentage: 34,
3022 },
3023 ];
3024 let vote = Vote::Approve(choices.clone());
3025
3026 assert_eq!(proposal.options.len(), choices.len());
3028
3029 let result = proposal.assert_valid_vote(&vote);
3031
3032 assert_eq!(
3034 result,
3035 Err(GovernanceError::TotalVoteWeightMustBe100Percent.into())
3036 );
3037 }
3038
3039 #[test]
3040 pub fn test_assert_valid_vote_with_overflow_weight_for_multi_weighted_choice_error() {
3041 let mut proposal = create_test_multi_option_proposal();
3044 proposal.vote_type = VoteType::MultiChoice {
3045 choice_type: MultiChoiceType::Weighted,
3046 min_voter_options: 1,
3047 max_voter_options: 3,
3048 max_winning_options: 3,
3049 };
3050
3051 let choices = vec![
3052 VoteChoice {
3053 rank: 0,
3054 weight_percentage: 100,
3055 },
3056 VoteChoice {
3057 rank: 0,
3058 weight_percentage: 100,
3059 },
3060 VoteChoice {
3061 rank: 0,
3062 weight_percentage: 100,
3063 },
3064 ];
3065 let vote = Vote::Approve(choices.clone());
3066
3067 assert_eq!(proposal.options.len(), choices.len());
3069
3070 let result = proposal.assert_valid_vote(&vote);
3072
3073 assert_eq!(
3075 result,
3076 Err(GovernanceError::TotalVoteWeightMustBe100Percent.into())
3077 );
3078 }
3079
3080 #[test]
3081 pub fn test_assert_valid_proposal_options_with_invalid_choice_number_for_multi_weighted_choice_vote_error(
3082 ) {
3083 let vote_type = VoteType::MultiChoice {
3085 choice_type: MultiChoiceType::Weighted,
3086 min_voter_options: 1,
3087 max_voter_options: 3,
3088 max_winning_options: 3,
3089 };
3090
3091 let options = vec!["option 1".to_string(), "option 2".to_string()];
3092
3093 let result = assert_valid_proposal_options(&options, &vote_type);
3095
3096 assert_eq!(
3098 result,
3099 Err(GovernanceError::InvalidMultiChoiceProposalParameters.into())
3100 );
3101 }
3102
3103 #[test]
3104 pub fn test_assert_valid_proposal_options_with_no_options_for_multi_weighted_choice_vote_error()
3105 {
3106 let vote_type = VoteType::MultiChoice {
3108 choice_type: MultiChoiceType::Weighted,
3109 min_voter_options: 1,
3110 max_voter_options: 3,
3111 max_winning_options: 3,
3112 };
3113
3114 let options = vec![];
3115
3116 let result = assert_valid_proposal_options(&options, &vote_type);
3118
3119 assert_eq!(result, Err(GovernanceError::InvalidProposalOptions.into()));
3121 }
3122
3123 #[test]
3124 pub fn test_assert_valid_proposal_options_for_multi_weighted_choice_vote() {
3125 let vote_type = VoteType::MultiChoice {
3127 choice_type: MultiChoiceType::Weighted,
3128 min_voter_options: 1,
3129 max_voter_options: 3,
3130 max_winning_options: 3,
3131 };
3132
3133 let options = vec![
3134 "option 1".to_string(),
3135 "option 2".to_string(),
3136 "option 3".to_string(),
3137 ];
3138
3139 let result = assert_valid_proposal_options(&options, &vote_type);
3141
3142 assert_eq!(result, Ok(()));
3144 }
3145
3146 #[test]
3147 pub fn test_assert_valid_proposal_options_for_multi_weighted_choice_vote_with_empty_option_error(
3148 ) {
3149 let vote_type = VoteType::MultiChoice {
3151 choice_type: MultiChoiceType::Weighted,
3152 min_voter_options: 1,
3153 max_voter_options: 3,
3154 max_winning_options: 3,
3155 };
3156
3157 let options = vec![
3158 "".to_string(),
3159 "option 2".to_string(),
3160 "option 3".to_string(),
3161 ];
3162
3163 let result = assert_valid_proposal_options(&options, &vote_type);
3165
3166 assert_eq!(result, Err(GovernanceError::InvalidProposalOptions.into()));
3168 }
3169
3170 #[test]
3171 pub fn test_assert_more_than_ten_proposal_options_for_multi_weighted_choice_error() {
3172 let vote_type = VoteType::MultiChoice {
3174 choice_type: MultiChoiceType::Weighted,
3175 min_voter_options: 1,
3176 max_voter_options: 3,
3177 max_winning_options: 3,
3178 };
3179
3180 let options = vec![
3181 "option 1".to_string(),
3182 "option 2".to_string(),
3183 "option 3".to_string(),
3184 "option 4".to_string(),
3185 "option 5".to_string(),
3186 "option 6".to_string(),
3187 "option 7".to_string(),
3188 "option 8".to_string(),
3189 "option 9".to_string(),
3190 "option 10".to_string(),
3191 "option 11".to_string(),
3192 ];
3193
3194 let result = assert_valid_proposal_options(&options, &vote_type);
3196
3197 assert_eq!(result, Err(GovernanceError::InvalidProposalOptions.into()));
3199 }
3200
3201 #[test]
3202 fn test_proposal_v1_to_v2_serialisation_roundtrip() {
3203 let proposal_v1_source = ProposalV1 {
3206 account_type: GovernanceAccountType::ProposalV1,
3207 governance: Pubkey::new_unique(),
3208 governing_token_mint: Pubkey::new_unique(),
3209 state: ProposalState::Executing,
3210 token_owner_record: Pubkey::new_unique(),
3211 signatories_count: 5,
3212 signatories_signed_off_count: 4,
3213 yes_votes_count: 100,
3214 no_votes_count: 80,
3215 instructions_executed_count: 7,
3216 instructions_count: 8,
3217 instructions_next_index: 9,
3218 draft_at: 200,
3219 signing_off_at: Some(201),
3220 voting_at: Some(202),
3221 voting_at_slot: Some(203),
3222 voting_completed_at: Some(204),
3223 executing_at: Some(205),
3224 closed_at: Some(206),
3225 execution_flags: InstructionExecutionFlags::None,
3226 max_vote_weight: Some(250),
3227 vote_threshold: Some(VoteThreshold::YesVotePercentage(65)),
3228 name: "proposal".to_string(),
3229 description_link: "proposal-description".to_string(),
3230 };
3231
3232 let mut account_data = vec![];
3233 proposal_v1_source.serialize(&mut account_data).unwrap();
3234
3235 let program_id = Pubkey::new_unique();
3236
3237 let info_key = Pubkey::new_unique();
3238 let mut lamports = 10u64;
3239
3240 let account_info = AccountInfo::new(
3241 &info_key,
3242 false,
3243 false,
3244 &mut lamports,
3245 &mut account_data[..],
3246 &program_id,
3247 false,
3248 Epoch::default(),
3249 );
3250
3251 let proposal_v2 = get_proposal_data(&program_id, &account_info).unwrap();
3254
3255 proposal_v2
3256 .serialize(&mut account_info.data.borrow_mut()[..])
3257 .unwrap();
3258
3259 let proposal_v1_target =
3261 get_account_data::<ProposalV1>(&program_id, &account_info).unwrap();
3262
3263 assert_eq!(proposal_v1_source, proposal_v1_target)
3264 }
3265}