@@ -260,25 +260,88 @@ impl ProposalV2 {
260
260
Ok ( ( ) )
261
261
}
262
262
263
- /// Checks if Proposal can be voted on
263
+ /// Checks if the given vote kind can be cast for the Proposal
264
264
pub fn assert_can_cast_vote (
265
265
& self ,
266
- config : & GovernanceConfig ,
266
+ vote_kind : & VoteKind ,
267
+ governance_config : & GovernanceConfig ,
267
268
current_unix_timestamp : UnixTimestamp ,
268
269
) -> 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
269
287
self . assert_is_voting_state ( )
270
288
. map_err ( |_| GovernanceError :: InvalidStateCannotVote ) ?;
271
289
290
+ // The Proposal can be still in Voting state after the voting time ends and before it's finalized
272
291
// 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) {
274
293
return Err ( GovernanceError :: ProposalVotingTimeExpired . into ( ) ) ;
275
294
}
276
295
277
296
Ok ( ( ) )
278
297
}
279
298
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 {
282
345
self . voting_at
283
346
. unwrap ( )
284
347
. checked_add ( config. max_voting_time as i64 )
@@ -292,7 +355,7 @@ impl ProposalV2 {
292
355
current_unix_timestamp : UnixTimestamp ,
293
356
) -> bool {
294
357
// 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
296
359
}
297
360
298
361
/// Checks if Proposal can be finalized
@@ -324,7 +387,7 @@ impl ProposalV2 {
324
387
self . assert_can_finalize_vote ( config, current_unix_timestamp) ?;
325
388
326
389
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) ) ;
328
391
329
392
// Capture vote params to correctly display historical results
330
393
self . max_vote_weight = Some ( max_voter_weight) ;
@@ -1652,7 +1715,7 @@ mod test {
1652
1715
// Assert
1653
1716
assert_eq!( proposal. state, test_case. expected_finalized_state, "CASE: {:?}" , test_case) ;
1654
1717
assert_eq!(
1655
- Some ( proposal. vote_end_time ( & governance_config) ) ,
1718
+ Some ( proposal. expected_vote_end_time ( & governance_config) ) ,
1656
1719
proposal. voting_completed_at
1657
1720
) ;
1658
1721
@@ -2227,9 +2290,11 @@ mod test {
2227
2290
let current_timestamp =
2228
2291
proposal. voting_at . unwrap ( ) + governance_config. max_voting_time as i64 + 1 ;
2229
2292
2293
+ let vote_kind = VoteKind :: Electorate ;
2294
+
2230
2295
// Act
2231
2296
let err = proposal
2232
- . assert_can_cast_vote ( & governance_config, current_timestamp)
2297
+ . assert_can_cast_vote ( & vote_kind , & governance_config, current_timestamp)
2233
2298
. err ( )
2234
2299
. unwrap ( ) ;
2235
2300
@@ -2247,13 +2312,62 @@ mod test {
2247
2312
let current_timestamp =
2248
2313
proposal. voting_at . unwrap ( ) + governance_config. max_voting_time as i64 ;
2249
2314
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
+
2250
2338
// 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) ;
2252
2341
2253
2342
// Assert
2254
2343
assert_eq ! ( result, Ok ( ( ) ) ) ;
2255
2344
}
2256
2345
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
+
2257
2371
#[ test]
2258
2372
pub fn test_assert_valid_vote_with_deny_vote_for_survey_only_proposal_error ( ) {
2259
2373
// Arrange
@@ -2270,6 +2384,52 @@ mod test {
2270
2384
assert_eq ! ( result, Err ( GovernanceError :: InvalidVote . into( ) ) ) ;
2271
2385
}
2272
2386
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
+
2273
2433
#[ test]
2274
2434
pub fn test_assert_valid_vote_with_too_many_options_error ( ) {
2275
2435
// Arrange
0 commit comments