From 910c7a149a02cfd0e617fbfa00048c63bd43000f Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 22 Aug 2024 10:24:39 +0200 Subject: [PATCH] cannot vote a closed poll --- src/did/src/lib.rs | 10 ++- src/upgrader_canister/src/canister.rs | 18 +++++- src/upgrader_canister/src/lib.rs | 1 + src/upgrader_canister/src/state/polls.rs | 80 ++++++++++++++++++------ 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/did/src/lib.rs b/src/did/src/lib.rs index 879222e..209ce20 100644 --- a/src/did/src/lib.rs +++ b/src/did/src/lib.rs @@ -92,12 +92,18 @@ impl Storable for ProjectData { Debug, Clone, CandidType, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, )] pub struct Poll { + /// The description of the poll. pub description: String, + /// The type of poll. pub poll_type: PollType, + /// The list of principals that voted no. pub no_voters: Vec, + /// The list of principals that voted yes. pub yes_voters: Vec, - pub created_timestamp_millis: u64, - pub end_timestamp_millis: u64, + /// The timestamp when the poll was created. + pub created_timestamp_secs: u64, + /// The timestamp when the poll ends. + pub end_timestamp_secs: u64, } /// Describes the type of poll. diff --git a/src/upgrader_canister/src/canister.rs b/src/upgrader_canister/src/canister.rs index 35f6777..04534c6 100644 --- a/src/upgrader_canister/src/canister.rs +++ b/src/upgrader_canister/src/canister.rs @@ -142,8 +142,24 @@ impl UpgraderCanister { STATE.with(|state| { let caller = ic::caller(); state.permissions.borrow().check_has_all_permissions(&caller, &[Permission::VotePoll])?; - state.polls.borrow_mut().vote(poll_id, caller, approved) + state.polls.borrow_mut().vote(poll_id, caller, approved, time_secs()) }) } } + +/// returns the timestamp in seconds +#[inline] +pub fn time_secs() -> u64 { + #[cfg(not(target_family = "wasm"))] + { + std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("get current timestamp error") + .as_secs() + } + + // ic::time() return the nano_sec, we need to change it to sec. + #[cfg(target_family = "wasm")] + (ic_exports::ic_kit::ic::time() / crate::constant::E_9) +} \ No newline at end of file diff --git a/src/upgrader_canister/src/lib.rs b/src/upgrader_canister/src/lib.rs index 4a8d918..4b98c8b 100644 --- a/src/upgrader_canister/src/lib.rs +++ b/src/upgrader_canister/src/lib.rs @@ -7,6 +7,7 @@ pub mod state; pub fn idl() -> String { + use std::collections::BTreeMap; use candid::Principal; use ic_canister::Idl; use upgrader_canister_did::*; diff --git a/src/upgrader_canister/src/state/polls.rs b/src/upgrader_canister/src/state/polls.rs index 2f42c72..5112271 100644 --- a/src/upgrader_canister/src/state/polls.rs +++ b/src/upgrader_canister/src/state/polls.rs @@ -43,11 +43,18 @@ impl Polls { } /// Votes for a poll. If the voter has already voted, the previous vote is replaced. - pub fn vote(&mut self, poll_id: u64, voter_principal: Principal, approved: bool) -> Result<()> { + pub fn vote(&mut self, poll_id: u64, voter_principal: Principal, approved: bool, timestamp_secs: u64) -> Result<()> { let mut poll = self.polls.get(&poll_id).ok_or_else(|| { UpgraderError::BadRequest(format!("Poll with id {} not found", poll_id)) })?; + // Check if the poll is closed + if timestamp_secs > poll.end_timestamp_secs { + return Err(UpgraderError::BadRequest( + "The poll is closed".to_string(), + )); + } + // Remove the voter from the previous vote poll.yes_voters.retain(|x| x != &voter_principal); poll.no_voters.retain(|x| x != &voter_principal); @@ -75,6 +82,8 @@ impl Polls { #[cfg(test)] mod test { + use std::u64; + use candid::Principal; use upgrader_canister_did::PollType; @@ -104,8 +113,8 @@ mod test { project: "project".to_owned(), hash: "hash".to_owned(), }, - created_timestamp_millis: 123456, - end_timestamp_millis: 234567, + created_timestamp_secs: 123456, + end_timestamp_secs: 234567, }); let poll_1_id = polls.insert(upgrader_canister_did::Poll { @@ -116,8 +125,8 @@ mod test { project: "project".to_owned(), hash: "hash".to_owned(), }, - created_timestamp_millis: 123456, - end_timestamp_millis: 234567, + created_timestamp_secs: 123456, + end_timestamp_secs: 234567, }); // Assert @@ -134,7 +143,7 @@ mod test { let mut polls = super::Polls::new(&memory_manager); // Act - let result = polls.vote(0, candid::Principal::anonymous(), true); + let result = polls.vote(0, candid::Principal::anonymous(), true, 0); // Assert assert!(result.is_err()); @@ -154,8 +163,8 @@ mod test { project: "project".to_owned(), hash: "hash".to_owned(), }, - created_timestamp_millis: 123456, - end_timestamp_millis: 234567, + created_timestamp_secs: 123456, + end_timestamp_secs: 234567, }); let principal_1 = Principal::from_slice(&[1, 29]); @@ -163,9 +172,9 @@ mod test { let principal_3 = Principal::from_slice(&[3, 29]); // Act - polls.vote(poll_id, principal_1, true).unwrap(); - polls.vote(poll_id, principal_2, false).unwrap(); - polls.vote(poll_id, principal_3, true).unwrap(); + polls.vote(poll_id, principal_1, true, 0).unwrap(); + polls.vote(poll_id, principal_2, false, 0).unwrap(); + polls.vote(poll_id, principal_3, true, 0).unwrap(); // Assert let poll = polls.get(&poll_id).unwrap(); @@ -191,8 +200,8 @@ mod test { project: "project".to_owned(), hash: "hash".to_owned(), }, - created_timestamp_millis: 123456, - end_timestamp_millis: 234567, + created_timestamp_secs: 123456, + end_timestamp_secs: 234567, }); let principal_1 = Principal::from_slice(&[1, 29]); @@ -201,12 +210,12 @@ mod test { let principal_4 = Principal::from_slice(&[4, 29]); // Act - polls.vote(poll_id, principal_1, true).unwrap(); - polls.vote(poll_id, principal_2, true).unwrap(); - polls.vote(poll_id, principal_3, false).unwrap(); - polls.vote(poll_id, principal_4, false).unwrap(); - polls.vote(poll_id, principal_1, false).unwrap(); - polls.vote(poll_id, principal_4, true).unwrap(); + polls.vote(poll_id, principal_1, true, 0).unwrap(); + polls.vote(poll_id, principal_2, true, 0).unwrap(); + polls.vote(poll_id, principal_3, false, 0).unwrap(); + polls.vote(poll_id, principal_4, false, 0).unwrap(); + polls.vote(poll_id, principal_1, false, 0).unwrap(); + polls.vote(poll_id, principal_4, true, 0).unwrap(); // Assert let poll = polls.get(&poll_id).unwrap(); @@ -218,4 +227,37 @@ mod test { assert!(poll.no_voters.contains(&principal_1)); assert!(poll.no_voters.contains(&principal_3)); } + + /// Should return an error if the poll is closed + #[test] + fn test_vote_closed_poll() { + // Arrange + let memory_manager = ic_stable_structures::default_ic_memory_manager(); + let mut polls = super::Polls::new(&memory_manager); + + let end_ts = 100; + + let poll_id = polls.insert(upgrader_canister_did::Poll { + description: "poll_0".to_string(), + yes_voters: vec![], + no_voters: vec![], + poll_type: PollType::ProjectHash { + project: "project".to_owned(), + hash: "hash".to_owned(), + }, + created_timestamp_secs: 123456, + end_timestamp_secs: end_ts, + }); + + let principal_1 = Principal::from_slice(&[1, 29]); + + // Act & Assert + assert!(polls.vote(poll_id, principal_1, true, 0).is_ok()); + assert!(polls.vote(poll_id, principal_1, true, end_ts-1).is_ok()); + assert!(polls.vote(poll_id, principal_1, true, end_ts).is_ok()); + assert!(polls.vote(poll_id, principal_1, true, end_ts+1).is_err()); + assert!(polls.vote(poll_id, principal_1, true, u64::MAX).is_err()); + + + } }