Skip to content

Commit

Permalink
cannot vote a closed poll
Browse files Browse the repository at this point in the history
  • Loading branch information
ufoscout committed Aug 22, 2024
1 parent 6490b3e commit 910c7a1
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 22 deletions.
10 changes: 8 additions & 2 deletions src/did/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Principal>,
/// The list of principals that voted yes.
pub yes_voters: Vec<Principal>,
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.
Expand Down
18 changes: 17 additions & 1 deletion src/upgrader_canister/src/canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
1 change: 1 addition & 0 deletions src/upgrader_canister/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
80 changes: 61 additions & 19 deletions src/upgrader_canister/src/state/polls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,18 @@ impl<M: Memory> Polls<M> {
}

/// 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);
Expand Down Expand Up @@ -75,6 +82,8 @@ impl<M: Memory> Polls<M> {

#[cfg(test)]
mod test {
use std::u64;

use candid::Principal;
use upgrader_canister_did::PollType;

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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());
Expand All @@ -154,18 +163,18 @@ 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]);
let principal_2 = Principal::from_slice(&[2, 29]);
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();
Expand All @@ -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]);
Expand All @@ -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();
Expand All @@ -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());


}
}

0 comments on commit 910c7a1

Please sign in to comment.