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

Commit 84e5c83

Browse files
authored
Governance: Extend Veto vote period by min_transaction_hold_up_time (#3808)
* feat: Extend Veto vote period by min_transaction_hold_up_time * chore: add veto vote within hold up time tests * chore: test_cast_council_veto_vote_within_hold_up_time * chore: try to limit no of jobs to fix build * chore: limit jobs to 1 * Revert "chore: limit jobs to 1" This reverts commit 03191b8. * Revert "chore: try to limit no of jobs to fix build" This reverts commit 0adce58. * fix: use saturating_add * chore: rename vote_end_time() to expected_vote_end_time()
1 parent 6725958 commit 84e5c83

File tree

5 files changed

+241
-13
lines changed

5 files changed

+241
-13
lines changed

governance/program/src/processor/process_cast_vote.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ 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(
86+
&vote_kind,
87+
&governance_data.config,
88+
clock.unix_timestamp,
89+
)?;
8690

8791
let mut voter_token_owner_record_data =
8892
get_token_owner_record_data_for_realm_and_governing_mint(

governance/program/src/state/governance.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub struct GovernanceConfig {
3131
pub min_community_weight_to_create_proposal: u64,
3232

3333
/// Minimum waiting time in seconds for a transaction to be executed after proposal is voted on
34+
/// If Veto vote is enabled then it can also be cast within the hold up period after Proposal vote ends
3435
pub min_transaction_hold_up_time: u32,
3536

3637
/// Time limit in seconds for proposal to be open for voting

governance/program/src/state/proposal.rs

Lines changed: 170 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -260,25 +260,88 @@ impl ProposalV2 {
260260
Ok(())
261261
}
262262

263-
/// Checks if Proposal can be voted on
263+
/// Checks if the given vote kind can be cast for the Proposal
264264
pub fn assert_can_cast_vote(
265265
&self,
266-
config: &GovernanceConfig,
266+
vote_kind: &VoteKind,
267+
governance_config: &GovernanceConfig,
267268
current_unix_timestamp: UnixTimestamp,
268269
) -> Result<(), ProgramError> {
270+
match vote_kind {
271+
VoteKind::Electorate => {
272+
self.assert_can_cast_electorate_vote(governance_config, current_unix_timestamp)
273+
}
274+
VoteKind::Veto => {
275+
self.assert_can_cast_veto_vote(governance_config, current_unix_timestamp)
276+
}
277+
}
278+
}
279+
280+
/// Checks if Electorate vote can be cast for the Proposal
281+
pub fn assert_can_cast_electorate_vote(
282+
&self,
283+
governance_config: &GovernanceConfig,
284+
current_unix_timestamp: UnixTimestamp,
285+
) -> Result<(), ProgramError> {
286+
// Electorate vote can only be cast in Voting state
269287
self.assert_is_voting_state()
270288
.map_err(|_| GovernanceError::InvalidStateCannotVote)?;
271289

290+
// The Proposal can be still in Voting state after the voting time ends and before it's finalized
272291
// Check if we are still within the configured max_voting_time period
273-
if self.has_vote_time_ended(config, current_unix_timestamp) {
292+
if self.has_vote_time_ended(governance_config, current_unix_timestamp) {
274293
return Err(GovernanceError::ProposalVotingTimeExpired.into());
275294
}
276295

277296
Ok(())
278297
}
279298

280-
/// Vote end time determined by the configured max_voting_time period
281-
pub fn vote_end_time(&self, config: &GovernanceConfig) -> UnixTimestamp {
299+
/// Checks if Veto vote can be cast for the Proposal
300+
pub fn assert_can_cast_veto_vote(
301+
&self,
302+
governance_config: &GovernanceConfig,
303+
current_unix_timestamp: UnixTimestamp,
304+
) -> Result<(), ProgramError> {
305+
match self.state {
306+
// Veto vote can be cast when:
307+
// Proposal is in Voting state and within max_voting_time + min_transaction_hold_up_time period
308+
ProposalState::Voting => {
309+
if self
310+
.expected_vote_end_time(governance_config)
311+
.saturating_add(governance_config.min_transaction_hold_up_time as i64)
312+
< current_unix_timestamp
313+
{
314+
Err(GovernanceError::ProposalVotingTimeExpired.into())
315+
} else {
316+
Ok(())
317+
}
318+
}
319+
// Proposal is in Succeeded state and within min_transaction_hold_up_time period calculated from the time when the vote ended (voting_completed_at)
320+
ProposalState::Succeeded => {
321+
if self
322+
.voting_completed_at
323+
.unwrap()
324+
.saturating_add(governance_config.min_transaction_hold_up_time as i64)
325+
< current_unix_timestamp
326+
{
327+
Err(GovernanceError::ProposalVotingTimeExpired.into())
328+
} else {
329+
Ok(())
330+
}
331+
}
332+
ProposalState::Draft
333+
| ProposalState::Executing
334+
| ProposalState::ExecutingWithErrors
335+
| ProposalState::Completed
336+
| ProposalState::Cancelled
337+
| ProposalState::SigningOff
338+
| ProposalState::Defeated
339+
| ProposalState::Vetoed => Err(GovernanceError::InvalidStateCannotVote.into()),
340+
}
341+
}
342+
343+
/// Expected vote end time determined by the configured max_voting_time period
344+
pub fn expected_vote_end_time(&self, config: &GovernanceConfig) -> UnixTimestamp {
282345
self.voting_at
283346
.unwrap()
284347
.checked_add(config.max_voting_time as i64)
@@ -292,7 +355,7 @@ impl ProposalV2 {
292355
current_unix_timestamp: UnixTimestamp,
293356
) -> bool {
294357
// Check if we passed vote_end_time
295-
self.vote_end_time(config) < current_unix_timestamp
358+
self.expected_vote_end_time(config) < current_unix_timestamp
296359
}
297360

298361
/// Checks if Proposal can be finalized
@@ -324,7 +387,7 @@ impl ProposalV2 {
324387
self.assert_can_finalize_vote(config, current_unix_timestamp)?;
325388

326389
self.state = self.resolve_final_vote_state(max_voter_weight, vote_threshold)?;
327-
self.voting_completed_at = Some(self.vote_end_time(config));
390+
self.voting_completed_at = Some(self.expected_vote_end_time(config));
328391

329392
// Capture vote params to correctly display historical results
330393
self.max_vote_weight = Some(max_voter_weight);
@@ -1652,7 +1715,7 @@ mod test {
16521715
// Assert
16531716
assert_eq!(proposal.state,test_case.expected_finalized_state,"CASE: {:?}",test_case);
16541717
assert_eq!(
1655-
Some(proposal.vote_end_time(&governance_config)),
1718+
Some(proposal.expected_vote_end_time(&governance_config)),
16561719
proposal.voting_completed_at
16571720
);
16581721

@@ -2227,9 +2290,11 @@ mod test {
22272290
let current_timestamp =
22282291
proposal.voting_at.unwrap() + governance_config.max_voting_time as i64 + 1;
22292292

2293+
let vote_kind = VoteKind::Electorate;
2294+
22302295
// Act
22312296
let err = proposal
2232-
.assert_can_cast_vote(&governance_config, current_timestamp)
2297+
.assert_can_cast_vote(&vote_kind, &governance_config, current_timestamp)
22332298
.err()
22342299
.unwrap();
22352300

@@ -2247,13 +2312,62 @@ mod test {
22472312
let current_timestamp =
22482313
proposal.voting_at.unwrap() + governance_config.max_voting_time as i64;
22492314

2315+
let vote_kind = VoteKind::Electorate;
2316+
2317+
// Act
2318+
let result =
2319+
proposal.assert_can_cast_vote(&vote_kind, &governance_config, current_timestamp);
2320+
2321+
// Assert
2322+
assert_eq!(result, Ok(()));
2323+
}
2324+
2325+
#[test]
2326+
pub fn test_assert_can_cast_veto_vote_for_voting_proposal_within_hold_up_time() {
2327+
// Arrange
2328+
let mut proposal = create_test_proposal();
2329+
proposal.state = ProposalState::Voting;
2330+
let governance_config = create_test_governance_config();
2331+
2332+
let current_timestamp = proposal.voting_at.unwrap()
2333+
+ governance_config.max_voting_time as i64
2334+
+ governance_config.min_transaction_hold_up_time as i64;
2335+
2336+
let vote_kind = VoteKind::Veto;
2337+
22502338
// Act
2251-
let result = proposal.assert_can_cast_vote(&governance_config, current_timestamp);
2339+
let result =
2340+
proposal.assert_can_cast_vote(&vote_kind, &governance_config, current_timestamp);
22522341

22532342
// Assert
22542343
assert_eq!(result, Ok(()));
22552344
}
22562345

2346+
#[test]
2347+
pub fn test_assert_can_cast_veto_vote_for_voting_proposal_with_cannot_cast_after_hold_up_time_error(
2348+
) {
2349+
// Arrange
2350+
let mut proposal = create_test_proposal();
2351+
proposal.state = ProposalState::Voting;
2352+
let governance_config = create_test_governance_config();
2353+
2354+
let current_timestamp = proposal.voting_at.unwrap()
2355+
+ governance_config.max_voting_time as i64
2356+
+ governance_config.min_transaction_hold_up_time as i64
2357+
+ 1;
2358+
2359+
let vote_kind = VoteKind::Veto;
2360+
2361+
// Act
2362+
let err = proposal
2363+
.assert_can_cast_vote(&vote_kind, &governance_config, current_timestamp)
2364+
.err()
2365+
.unwrap();
2366+
2367+
// Assert
2368+
assert_eq!(err, GovernanceError::ProposalVotingTimeExpired.into());
2369+
}
2370+
22572371
#[test]
22582372
pub fn test_assert_valid_vote_with_deny_vote_for_survey_only_proposal_error() {
22592373
// Arrange
@@ -2270,6 +2384,52 @@ mod test {
22702384
assert_eq!(result, Err(GovernanceError::InvalidVote.into()));
22712385
}
22722386

2387+
#[test]
2388+
pub fn test_assert_can_cast_veto_vote_for_succeeded_proposal_within_hold_up_time() {
2389+
// Arrange
2390+
let mut proposal = create_test_proposal();
2391+
proposal.state = ProposalState::Succeeded;
2392+
2393+
let governance_config = create_test_governance_config();
2394+
2395+
let current_timestamp = proposal.voting_completed_at.unwrap()
2396+
+ governance_config.min_transaction_hold_up_time as i64;
2397+
2398+
let vote_kind = VoteKind::Veto;
2399+
2400+
// Act
2401+
let result =
2402+
proposal.assert_can_cast_vote(&vote_kind, &governance_config, current_timestamp);
2403+
2404+
// Assert
2405+
assert_eq!(result, Ok(()));
2406+
}
2407+
2408+
#[test]
2409+
pub fn test_assert_can_cast_veto_vote_for_succeeded_proposal_with_cannot_cast_after_hold_up_time_error(
2410+
) {
2411+
// Arrange
2412+
let mut proposal = create_test_proposal();
2413+
proposal.state = ProposalState::Succeeded;
2414+
2415+
let governance_config = create_test_governance_config();
2416+
2417+
let current_timestamp = proposal.voting_completed_at.unwrap()
2418+
+ governance_config.min_transaction_hold_up_time as i64
2419+
+ 1;
2420+
2421+
let vote_kind = VoteKind::Veto;
2422+
2423+
// Act
2424+
let err = proposal
2425+
.assert_can_cast_vote(&vote_kind, &governance_config, current_timestamp)
2426+
.err()
2427+
.unwrap();
2428+
2429+
// Assert
2430+
assert_eq!(err, GovernanceError::ProposalVotingTimeExpired.into());
2431+
}
2432+
22732433
#[test]
22742434
pub fn test_assert_valid_vote_with_too_many_options_error() {
22752435
// Arrange

governance/program/tests/process_finalize_vote.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ async fn test_finalize_vote_to_succeeded() {
8282

8383
assert_eq!(proposal_account.state, ProposalState::Succeeded);
8484
assert_eq!(
85-
Some(proposal_account.vote_end_time(&governance_cookie.account.config)),
85+
Some(proposal_account.expected_vote_end_time(&governance_cookie.account.config)),
8686
proposal_account.voting_completed_at
8787
);
8888

@@ -385,7 +385,7 @@ async fn test_finalize_council_vote() {
385385

386386
assert_eq!(proposal_account.state, ProposalState::Succeeded);
387387
assert_eq!(
388-
Some(proposal_account.vote_end_time(&governance_cookie.account.config)),
388+
Some(proposal_account.expected_vote_end_time(&governance_cookie.account.config)),
389389
proposal_account.voting_completed_at
390390
);
391391

governance/program/tests/use_veto_vote.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,3 +968,66 @@ async fn test_veto_vote_with_community_max_voter_weight_addin_and_veto_not_tippe
968968

969969
assert_eq!(proposal_account.state, ProposalState::Voting);
970970
}
971+
972+
#[tokio::test]
973+
async fn test_cast_council_veto_vote_within_hold_up_time() {
974+
// Arrange
975+
let mut governance_test = GovernanceProgramTest::start_new().await;
976+
977+
let realm_cookie = governance_test.with_realm().await;
978+
let governed_account_cookie = governance_test.with_governed_account().await;
979+
980+
let token_owner_record_cookie = governance_test
981+
.with_council_token_deposit(&realm_cookie)
982+
.await
983+
.unwrap();
984+
985+
// Mint extra council tokens for total supply of 120
986+
governance_test.mint_council_tokens(&realm_cookie, 20).await;
987+
988+
let mut governance_cookie = governance_test
989+
.with_governance(
990+
&realm_cookie,
991+
&governed_account_cookie,
992+
&token_owner_record_cookie,
993+
)
994+
.await
995+
.unwrap();
996+
997+
let proposal_owner_record_cookie = governance_test
998+
.with_community_token_deposit(&realm_cookie)
999+
.await
1000+
.unwrap();
1001+
1002+
let proposal_cookie = governance_test
1003+
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
1004+
.await
1005+
.unwrap();
1006+
1007+
governance_test
1008+
.with_cast_yes_no_vote(
1009+
&proposal_cookie,
1010+
&proposal_owner_record_cookie,
1011+
YesNoVote::Yes,
1012+
)
1013+
.await
1014+
.unwrap();
1015+
1016+
// Move the clock within the hold up time period
1017+
governance_test.advance_clock().await;
1018+
1019+
// Act
1020+
1021+
governance_test
1022+
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
1023+
.await
1024+
.unwrap();
1025+
1026+
// Assert
1027+
1028+
let proposal_account = governance_test
1029+
.get_proposal_account(&proposal_cookie.address)
1030+
.await;
1031+
1032+
assert_eq!(proposal_account.state, ProposalState::Vetoed);
1033+
}

0 commit comments

Comments
 (0)