Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 21cfaff

Browse files
authored
Governance: voting_cool_off_time (#3830)
* wip: add voting_cool_off_time * feat: Default voting_cool_off_time to 1h for 10h or longer votes * wip: Assert valid voting cool of time * chore: cleanup GovernanceConfig tests * chore: Rename vote_end_time() to expected_vote_end_time() * chore: test_cast_council_veto_vote_within_cool_off_time * chore: test_cast_approve_vote_with_cannot_vote_in_cool_off_time_error * chore: test_change_yes_vote_to_no_within_cool_off_time * chore: Reorder GovernanceConfig fields * chore: test_finalize_vote_with_cannot_finalize_during_cool_off_time_error * fix: Remove default voting_cool_off_time * fix: Use max = base + cool_off voting time setup * chore: Cleanup * chore: Fix test_change_yes_vote_to_no_within_cool_off_time * chore: Adjust base, cool off and max voting time names * chore: Create has_voting_base_time_ended
1 parent c24f9e8 commit 21cfaff

15 files changed

+648
-133
lines changed

governance/chat/program/tests/program_test/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,17 +186,18 @@ impl GovernanceChatProgramTest {
186186
let governed_account_address = Pubkey::new_unique();
187187

188188
let governance_config = GovernanceConfig {
189+
community_vote_threshold: VoteThreshold::YesVotePercentage(60),
189190
min_community_weight_to_create_proposal: 5,
190-
min_council_weight_to_create_proposal: 2,
191191
min_transaction_hold_up_time: 10,
192-
max_voting_time: 10,
193-
community_vote_threshold: VoteThreshold::YesVotePercentage(60),
192+
voting_base_time: 10,
194193
community_vote_tipping: spl_governance::state::enums::VoteTipping::Strict,
195194
council_vote_threshold: VoteThreshold::YesVotePercentage(10),
196195
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(50),
196+
min_council_weight_to_create_proposal: 2,
197197
council_vote_tipping: spl_governance::state::enums::VoteTipping::Strict,
198198
community_veto_vote_threshold: VoteThreshold::YesVotePercentage(55),
199-
reserved: [0; 5],
199+
voting_cool_off_time: 1,
200+
reserved: 0,
200201
};
201202

202203
let token_owner_record_address = get_token_owner_record_address(

governance/program/src/error.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,13 +431,17 @@ pub enum GovernanceError {
431431
#[error("Invalid GoverningToken source")]
432432
InvalidGoverningTokenSource, // 603
433433

434-
/// Cannot change community TokenType to Memebership
435-
#[error("Cannot change community TokenType to Memebership")]
436-
CannotChangeCommunityTokenTypeToMemebership, // 604
434+
/// Cannot change community TokenType to Membership
435+
#[error("Cannot change community TokenType to Membership")]
436+
CannotChangeCommunityTokenTypeToMembership, // 604
437437

438438
/// Voter weight threshold disabled
439439
#[error("Voter weight threshold disabled")]
440440
VoterWeightThresholdDisabled, // 605
441+
442+
/// Vote not allowed in cool off time
443+
#[error("Vote not allowed in cool off time")]
444+
VoteNotAllowedInCoolOffTime, // 606
441445
}
442446

443447
impl PrintProgramError for GovernanceError {

governance/program/src/processor/process_cast_vote.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub fn process_cast_vote(
8282
governance_info.key,
8383
&proposal_governing_token_mint,
8484
)?;
85-
proposal_data.assert_can_cast_vote(&governance_data.config, clock.unix_timestamp)?;
85+
proposal_data.assert_can_cast_vote(&governance_data.config, &vote, clock.unix_timestamp)?;
8686

8787
let mut voter_token_owner_record_data =
8888
get_token_owner_record_data_for_realm_and_governing_mint(

governance/program/src/processor/process_relinquish_vote.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ pub fn process_relinquish_vote(program_id: &Pubkey, accounts: &[AccountInfo]) ->
6565
let clock = Clock::get()?;
6666

6767
// If the Proposal is still being voted on then the token owner vote will be withdrawn and it won't count towards the vote outcome
68-
// Note: If there is no tipping point the proposal can be still in Voting state but already past the configured max_voting_time
68+
// Note: If there is no tipping point the proposal can be still in Voting state but already past the configured max voting time (base + cool off voting time)
6969
// It means it awaits manual finalization (FinalizeVote) and it should no longer be possible to withdraw the vote
7070
if proposal_data.state == ProposalState::Voting
71-
&& !proposal_data.has_vote_time_ended(&governance_data.config, clock.unix_timestamp)
71+
&& !proposal_data.has_voting_max_time_ended(&governance_data.config, clock.unix_timestamp)
7272
{
7373
let governance_authority_info = next_account_info(account_info_iter)?; // 5
7474
let beneficiary_info = next_account_info(account_info_iter)?; // 6

governance/program/src/state/governance.rs

Lines changed: 103 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ pub struct GovernanceConfig {
3333
/// Minimum waiting time in seconds for a transaction to be executed after proposal is voted on
3434
pub min_transaction_hold_up_time: u32,
3535

36-
/// Time limit in seconds for proposal to be open for voting
37-
pub max_voting_time: u32,
36+
/// The base voting time in seconds for proposal to be open for voting
37+
/// Voting is unrestricted during the base voting time and any vote types can be cast
38+
/// The base voting time can be extend by optional cool off time when only negative votes (Veto and Deny) are allowed
39+
pub voting_base_time: u32,
3840

3941
/// Conditions under which a Community vote will complete early
4042
pub community_vote_tipping: VoteTipping,
@@ -55,8 +57,11 @@ pub struct GovernanceConfig {
5557
/// The threshold for Community Veto votes
5658
pub community_veto_vote_threshold: VoteThreshold,
5759

60+
/// Voting cool of time
61+
pub voting_cool_off_time: u32,
62+
5863
/// Reserved space for future versions
59-
pub reserved: [u8; 5],
64+
pub reserved: u8,
6065
}
6166

6267
/// Governance Account
@@ -178,7 +183,7 @@ impl GovernanceV2 {
178183
} else if is_governance_v1_account_type(&self.account_type) {
179184
// V1 account can't be resized and we have to translate it back to the original format
180185

181-
// If reserved_v2 is used it must be individually assesed for v1 backward compatibility impact
186+
// If reserved_v2 is used it must be individually assessed for v1 backward compatibility impact
182187
if self.reserved_v2 != [0; 128] {
183188
panic!("Extended data not supported by GovernanceV1")
184189
}
@@ -288,13 +293,14 @@ pub fn get_governance_data(
288293

289294
// In previous versions of spl-gov (< 3) we had config.proposal_cool_off_time:u32 which was unused and always 0
290295
// In version 3.0.0 proposal_cool_off_time was replaced with council_vote_threshold:VoteThreshold and council_veto_vote_threshold:VoteThreshold
291-
//
292296
// If we read a legacy account then council_vote_threshold == VoteThreshold::YesVotePercentage(0)
293-
// and we coerce it to be equal to community_vote_threshold which was used for both council and community thresholds before
294297
//
295298
// Note: assert_is_valid_governance_config() prevents setting council_vote_threshold to VoteThreshold::YesVotePercentage(0)
296299
// which gives as guarantee that it is a legacy account layout set with proposal_cool_off_time = 0
300+
//
301+
// Note: All the settings below are one time config migration from V1 & V2 account data to V3
297302
if governance_data.config.council_vote_threshold == VoteThreshold::YesVotePercentage(0) {
303+
// Set council_vote_threshold to community_vote_threshold which was used for both council and community thresholds before
298304
governance_data.config.council_vote_threshold =
299305
governance_data.config.community_vote_threshold.clone();
300306

@@ -309,8 +315,9 @@ pub fn get_governance_data(
309315
// For legacy accounts set the community Veto threshold to Disabled
310316
governance_data.config.community_veto_vote_threshold = VoteThreshold::Disabled;
311317

312-
// Reset reserved space previously used for voting_proposal_count
313-
governance_data.config.reserved = [0; 5];
318+
// Reset voting_cool_off_time and reserved space previously used for voting_proposal_count
319+
governance_data.config.voting_cool_off_time = 0;
320+
governance_data.config.reserved = 0;
314321
}
315322

316323
Ok(governance_data)
@@ -478,6 +485,10 @@ pub fn assert_is_valid_governance_config(
478485
return Err(GovernanceError::AtLeastOneVoteThresholdRequired.into());
479486
}
480487

488+
if governance_config.reserved != 0 {
489+
return Err(GovernanceError::ReservedBufferMustBeEmpty.into());
490+
}
491+
481492
Ok(())
482493
}
483494

@@ -506,17 +517,18 @@ mod test {
506517

507518
fn create_test_governance_config() -> GovernanceConfig {
508519
GovernanceConfig {
520+
community_vote_threshold: VoteThreshold::YesVotePercentage(60),
509521
min_community_weight_to_create_proposal: 5,
510-
min_council_weight_to_create_proposal: 1,
511522
min_transaction_hold_up_time: 10,
512-
max_voting_time: 5,
513-
community_vote_threshold: VoteThreshold::YesVotePercentage(60),
523+
voting_base_time: 5,
514524
community_vote_tipping: VoteTipping::Strict,
515525
council_vote_threshold: VoteThreshold::YesVotePercentage(60),
516526
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(50),
527+
min_council_weight_to_create_proposal: 1,
517528
council_vote_tipping: VoteTipping::Strict,
518529
community_veto_vote_threshold: VoteThreshold::YesVotePercentage(40),
519-
reserved: [0; 5],
530+
voting_cool_off_time: 2,
531+
reserved: 0,
520532
}
521533
}
522534

@@ -621,25 +633,15 @@ mod test {
621633
governance.config.community_vote_tipping
622634
);
623635

624-
assert_eq!(governance.config.reserved, [0; 5]);
636+
assert_eq!(governance.config.reserved, 0);
637+
assert_eq!(governance.config.voting_cool_off_time, 0);
625638
}
626639

627640
#[test]
628641
fn test_assert_config_invalid_with_council_zero_yes_vote_threshold() {
629642
// Arrange
630-
let governance_config = GovernanceConfig {
631-
community_vote_threshold: VoteThreshold::YesVotePercentage(1),
632-
min_community_weight_to_create_proposal: 1,
633-
min_transaction_hold_up_time: 1,
634-
max_voting_time: 1,
635-
community_vote_tipping: VoteTipping::Strict,
636-
council_vote_threshold: VoteThreshold::YesVotePercentage(0),
637-
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
638-
min_council_weight_to_create_proposal: 1,
639-
council_vote_tipping: VoteTipping::Strict,
640-
community_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
641-
reserved: [0; 5],
642-
};
643+
let mut governance_config = create_test_governance_config();
644+
governance_config.council_vote_threshold = VoteThreshold::YesVotePercentage(0);
643645

644646
// Act
645647
let err = assert_is_valid_governance_config(&governance_config)
@@ -650,22 +652,77 @@ mod test {
650652
assert_eq!(err, GovernanceError::InvalidVoteThresholdPercentage.into());
651653
}
652654

655+
#[test]
656+
fn test_migrate_governance_config_from_legacy_data_to_v3() {
657+
// Arrange
658+
let mut governance_legacy_data = create_test_governance();
659+
660+
governance_legacy_data.config.community_vote_threshold =
661+
VoteThreshold::YesVotePercentage(60);
662+
663+
// council_vote_threshold == YesVotePercentage(0) indicates legacy account from V1 & V2 program versions
664+
governance_legacy_data.config.council_vote_threshold = VoteThreshold::YesVotePercentage(0);
665+
666+
governance_legacy_data.config.council_veto_vote_threshold =
667+
VoteThreshold::YesVotePercentage(0);
668+
governance_legacy_data.config.council_vote_tipping = VoteTipping::Disabled;
669+
governance_legacy_data.config.community_veto_vote_threshold =
670+
VoteThreshold::YesVotePercentage(0);
671+
governance_legacy_data.config.voting_cool_off_time = 1;
672+
governance_legacy_data.config.voting_base_time = 36000;
673+
674+
let mut legacy_data = vec![];
675+
governance_legacy_data.serialize(&mut legacy_data).unwrap();
676+
677+
let program_id = Pubkey::new_unique();
678+
679+
let info_key = Pubkey::new_unique();
680+
let mut lamports = 10u64;
681+
682+
let legacy_account_info = AccountInfo::new(
683+
&info_key,
684+
false,
685+
false,
686+
&mut lamports,
687+
&mut legacy_data[..],
688+
&program_id,
689+
false,
690+
Epoch::default(),
691+
);
692+
// Act
693+
let governance_v3 = get_governance_data(&program_id, &legacy_account_info).unwrap();
694+
695+
// Assert
696+
assert_eq!(
697+
governance_v3.config.council_vote_threshold,
698+
VoteThreshold::YesVotePercentage(60)
699+
);
700+
701+
assert_eq!(
702+
governance_v3.config.council_veto_vote_threshold,
703+
VoteThreshold::YesVotePercentage(60)
704+
);
705+
706+
assert_eq!(
707+
governance_v3.config.community_veto_vote_threshold,
708+
VoteThreshold::Disabled
709+
);
710+
711+
assert_eq!(
712+
governance_v3.config.council_vote_tipping,
713+
VoteTipping::Strict
714+
);
715+
716+
assert_eq!(governance_v3.config.voting_cool_off_time, 0);
717+
718+
assert_eq!(governance_v3.config.reserved, 0);
719+
}
720+
653721
#[test]
654722
fn test_assert_config_invalid_with_community_zero_yes_vote_threshold() {
655723
// Arrange
656-
let governance_config = GovernanceConfig {
657-
community_vote_threshold: VoteThreshold::YesVotePercentage(0),
658-
min_community_weight_to_create_proposal: 1,
659-
min_transaction_hold_up_time: 1,
660-
max_voting_time: 1,
661-
community_vote_tipping: VoteTipping::Strict,
662-
council_vote_threshold: VoteThreshold::YesVotePercentage(1),
663-
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
664-
min_council_weight_to_create_proposal: 1,
665-
council_vote_tipping: VoteTipping::Strict,
666-
community_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
667-
reserved: [0; 5],
668-
};
724+
let mut governance_config = create_test_governance_config();
725+
governance_config.community_vote_threshold = VoteThreshold::YesVotePercentage(0);
669726

670727
// Act
671728
let err = assert_is_valid_governance_config(&governance_config)
@@ -679,19 +736,9 @@ mod test {
679736
#[test]
680737
fn test_assert_config_invalid_with_all_vote_thresholds_disabled() {
681738
// Arrange
682-
let governance_config = GovernanceConfig {
683-
community_vote_threshold: VoteThreshold::Disabled,
684-
min_community_weight_to_create_proposal: 1,
685-
min_transaction_hold_up_time: 1,
686-
max_voting_time: 1,
687-
community_vote_tipping: VoteTipping::Strict,
688-
council_vote_threshold: VoteThreshold::Disabled,
689-
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
690-
min_council_weight_to_create_proposal: 1,
691-
council_vote_tipping: VoteTipping::Strict,
692-
community_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
693-
reserved: [0; 5],
694-
};
739+
let mut governance_config = create_test_governance_config();
740+
governance_config.community_vote_threshold = VoteThreshold::Disabled;
741+
governance_config.council_vote_threshold = VoteThreshold::Disabled;
695742

696743
// Act
697744
let err = assert_is_valid_governance_config(&governance_config)
@@ -705,19 +752,8 @@ mod test {
705752
#[test]
706753
fn test_assert_config_invalid_with_council_zero_yes_veto_vote_threshold() {
707754
// Arrange
708-
let governance_config = GovernanceConfig {
709-
community_vote_threshold: VoteThreshold::YesVotePercentage(1),
710-
min_community_weight_to_create_proposal: 1,
711-
min_transaction_hold_up_time: 1,
712-
max_voting_time: 1,
713-
community_vote_tipping: VoteTipping::Strict,
714-
council_vote_threshold: VoteThreshold::YesVotePercentage(1),
715-
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(0),
716-
min_council_weight_to_create_proposal: 1,
717-
council_vote_tipping: VoteTipping::Strict,
718-
community_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
719-
reserved: [0; 5],
720-
};
755+
let mut governance_config = create_test_governance_config();
756+
governance_config.council_veto_vote_threshold = VoteThreshold::YesVotePercentage(0);
721757

722758
// Act
723759
let err = assert_is_valid_governance_config(&governance_config)
@@ -731,19 +767,8 @@ mod test {
731767
#[test]
732768
fn test_assert_config_invalid_with_community_zero_yes_veto_vote_threshold() {
733769
// Arrange
734-
let governance_config = GovernanceConfig {
735-
community_vote_threshold: VoteThreshold::YesVotePercentage(1),
736-
min_community_weight_to_create_proposal: 1,
737-
min_transaction_hold_up_time: 1,
738-
max_voting_time: 1,
739-
council_vote_tipping: VoteTipping::Strict,
740-
council_vote_threshold: VoteThreshold::YesVotePercentage(1),
741-
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
742-
min_council_weight_to_create_proposal: 1,
743-
community_veto_vote_threshold: VoteThreshold::YesVotePercentage(0),
744-
community_vote_tipping: VoteTipping::Strict,
745-
reserved: [0; 5],
746-
};
770+
let mut governance_config = create_test_governance_config();
771+
governance_config.community_veto_vote_threshold = VoteThreshold::YesVotePercentage(0);
747772

748773
// Act
749774
let err = assert_is_valid_governance_config(&governance_config)

0 commit comments

Comments
 (0)