Skip to content

Commit 7751910

Browse files
committed
updated voting contract
1 parent 9497220 commit 7751910

File tree

10 files changed

+684
-132
lines changed

10 files changed

+684
-132
lines changed

contracts/voting/README.md

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# NEAR Voting Contract
2+
3+
A flexible and secure voting system built on NEAR Protocol that supports multiple election types, voting mechanisms, and voter eligibility verification.
4+
5+
## Purpose
6+
7+
This contract enables the creation and management of various types of elections on the NEAR blockchain, supporting features like:
8+
- Multiple voting types (Simple, Weighted)
9+
- Different election formats (General, Pot-based)
10+
- Configurable voter eligibility (Open, List-based, Token-based)
11+
- Secure vote recording and tallying
12+
- Transparent election results
13+
14+
## Key Features
15+
16+
### Election Types
17+
- **General Elections**: Standard voting process with multiple candidates
18+
- **Pot Elections**: Elections with an associated prize pool
19+
20+
### Voting Mechanisms
21+
- **Simple Voting**: One vote per voter per candidate
22+
- **Weighted Voting**: Voters can assign different weights to their votes (up to a maximum)
23+
24+
### Voter Eligibility
25+
- **Open**: Anyone can vote
26+
- **List-based**: Voters must be on a predefined list on potlock's list contract.
27+
- **Token-based**: Voters must hold a minimum token balance (planned)
28+
- **Custom**: Customizable eligibility criteria through external contracts
29+
30+
## Core Functions
31+
32+
### Election Management
33+
34+
```rust
35+
create_election(
36+
title: String,
37+
description: String,
38+
start_date: U64,
39+
end_date: U64,
40+
votes_per_voter: u32,
41+
voter_eligibility: String,
42+
voting_type: String,
43+
election_type: Value,
44+
candidates: Vec<AccountId>
45+
)
46+
```
47+
48+
- Creates a new election with specified parameters
49+
- Requires deposit for creation
50+
51+
### Candidate Operations
52+
53+
```rust
54+
apply(election_id: u64)
55+
```
56+
- Allows accounts to apply as candidates
57+
- Available during nomination period
58+
59+
### Voting Operations
60+
```rust
61+
vote(election_id: u64, vote: (AccountId, u32))
62+
```
63+
- Casts a vote for a candidate
64+
- Validates voter eligibility and voting rules
65+
66+
### Query Methods
67+
```rust
68+
get_election(election_id: &ElectionId) -> Option<Election>
69+
get_voter_votes(election_id: &ElectionId, voter: &AccountId) -> Option<Vec<Vote>>
70+
get_election_vote_count(election_id: &ElectionId) -> u64
71+
get_candidate_vote_weight(election_id: &ElectionId, candidate_id: &AccountId) -> u64
72+
is_voting_period(election_id: &ElectionId) -> bool
73+
```
74+
75+
## Usage
76+
77+
### Deployment
78+
```bash
79+
near deploy --wasmFile target/near/voting_contract.wasm --accountId your-contract.near
80+
```
81+
82+
### Initialize Contract
83+
```bash
84+
near call your-contract.near new '{"owner_id": "owner.near"}' --accountId owner.near
85+
```
86+
87+
### Create Election
88+
```bash
89+
near call your-contract.near create_election '{
90+
"title": "Test Election",
91+
"description": "Test Description",
92+
"start_date": "1234567890000000000",
93+
"end_date": "1234567890000000000",
94+
"votes_per_voter": 1,
95+
"voter_eligibility": "Open",
96+
"voting_type": "Simple",
97+
"election_type": {"General": null},
98+
"candidates": ["candidate1.near"]
99+
}' --accountId owner.near --deposit 1
100+
```
101+
102+
## Testing
103+
104+
Run the test suite:
105+
```bash
106+
cargo test
107+
```
108+
109+
Key test cases cover:
110+
- Election creation and management
111+
- Voting mechanics
112+
- Eligibility verification
113+
- Result calculation

contracts/voting/out/main.wasm

5.09 KB
Binary file not shown.

contracts/voting/src/elections.rs

+51-48
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use near_sdk::json_types::U128;
21
use crate::*;
3-
2+
use near_sdk::json_types::U128;
43

54
#[near(serializers=[borsh, json])]
65
#[derive(Clone, PartialEq)]
@@ -11,8 +10,6 @@ pub enum ElectionType {
1110
Custom(String, Option<AccountId>),
1211
}
1312

14-
15-
1613
#[near(serializers=[borsh, json])]
1714
#[derive(Clone, PartialEq)]
1815
pub enum ApplicationStatus {
@@ -25,10 +22,9 @@ pub enum ElectionPhase {
2522
Pending,
2623
Nomination,
2724
Voting,
28-
Ended
25+
Ended,
2926
}
3027

31-
3228
#[near(serializers=[borsh, json])]
3329
#[derive(Clone, PartialEq)]
3430
pub struct Candidate {
@@ -38,14 +34,13 @@ pub struct Candidate {
3834
pub application_date: U64,
3935
}
4036

41-
4237
#[near(serializers=[borsh, json])]
4338
#[derive(Clone, PartialEq)]
4439
pub enum EligibilityType {
4540
Open,
46-
ListBased(AccountId, U128), // list contract and id
41+
ListBased(AccountId, U128), // list contract and id
4742
TokenBased(AccountId, U128), // Token contract and minimum balance
48-
Custom(String), // Custom eligibility contract address
43+
Custom(String), // Custom eligibility contract address
4944
}
5045

5146
#[near(serializers=[borsh, json])]
@@ -59,7 +54,6 @@ pub enum ElectionStatus {
5954
Cancelled,
6055
}
6156

62-
6357
#[near(serializers=[borsh, json])]
6458
#[derive(Clone, PartialEq)]
6559
pub struct Election {
@@ -93,11 +87,14 @@ impl Contract {
9387
voter_eligibility: EligibilityType,
9488
voting_type: VotingType,
9589
election_type: ElectionType,
96-
candidates: Vec<AccountId>
90+
candidates: Vec<AccountId>,
9791
) -> ElectionId {
9892
self.assert_not_paused();
9993
self.assert_admin_or_owner();
100-
assert!(start_date.0 < end_date.0, "Start date must be before end date");
94+
assert!(
95+
start_date.0 < end_date.0,
96+
"Start date must be before end date"
97+
);
10198
let initial_storage_usage = env::storage_usage();
10299

103100
let election_id = self.election_counter;
@@ -124,12 +121,15 @@ impl Contract {
124121
let mut candidates_map: IterableMap<AccountId, Candidate> =
125122
IterableMap::new(StorageKey::Candidates { election_id });
126123
for candidate in candidates.clone() {
127-
candidates_map.insert(candidate.clone(), Candidate {
128-
account_id: candidate,
129-
status: ApplicationStatus::Approved,
130-
votes_received: 0,
131-
application_date: U64(env::block_timestamp()),
132-
});
124+
candidates_map.insert(
125+
candidate.clone(),
126+
Candidate {
127+
account_id: candidate,
128+
status: ApplicationStatus::Approved,
129+
votes_received: 0,
130+
application_date: U64(env::block_timestamp_ms()),
131+
},
132+
);
133133
}
134134
self.candidates.insert(election_id, candidates_map);
135135

@@ -148,8 +148,8 @@ impl Contract {
148148
#[payable]
149149
pub fn apply(&mut self, election_id: ElectionId) {
150150
// let election = self.elections.get(&election_id).expect("Election not found");
151-
// println!("Nomination end date: {:?}", env::block_timestamp());
152-
151+
// println!("Nomination end date: {:?}", env::block_timestamp_ms());
152+
153153
154154
// let applicant = env::predecessor_account_id();
155155
// // assert!(self.is_eligible_candidate(&election, &applicant), "Not eligible to be a candidate");
@@ -162,9 +162,9 @@ impl Contract {
162162
// ApplicationStatus::Pending
163163
// },
164164
// votes_received: 0,
165-
// application_date: U64(env::block_timestamp()),
165+
// application_date: U64(env::block_timestamp_ms()),
166166
// approval_date: if election.auto_approval {
167-
// Some(U64(env::block_timestamp()))
167+
// Some(U64(env::block_timestamp_ms()))
168168
// } else {
169169
// None
170170
// },
@@ -184,7 +184,7 @@ impl Contract {
184184
// ) {
185185
// // self.assert_not_paused();
186186
// self.assert_admin_or_owner();
187-
187+
188188
// let election = self.elections.get(&election_id).expect("Election not found");
189189
// let mut candidates_map = self.candidates
190190
// .get_mut(&election_id)
@@ -205,9 +205,9 @@ impl Contract {
205205
// account_id: candidate,
206206
// status: ApplicationStatus::Approved,
207207
// votes_received: 0,
208-
// application_date: U64(env::block_timestamp()),
208+
// application_date: U64(env::block_timestamp_ms()),
209209
// };
210-
210+
211211
// candidates_map.insert(candidate.account_id.clone(), candidate);
212212

213213
// }
@@ -216,7 +216,6 @@ impl Contract {
216216
// // candidates_map.insert(applicant, candidate);
217217
// // // self.candidates.insert(election_id, &candidates_map);
218218

219-
220219
// self.candidates.insert(election_id, candidates_map);
221220

222221
// // Refund excess deposit
@@ -235,16 +234,24 @@ impl Contract {
235234

236235
// candidate.status = status;
237236
// if matches!(candidate.status, ApplicationStatus::Approved) {
238-
// candidate.approval_date = Some(U64(env::block_timestamp()));
237+
// candidate.approval_date = Some(U64(env::block_timestamp_ms()));
239238
// }
240239
// }
241240

242-
pub fn get_elections(&self, from_index: Option<u128>, limit: Option<u128>,) -> Vec<Election> {
241+
pub fn get_elections(&self, from_index: Option<u128>, limit: Option<u128>) -> Vec<Election> {
243242
let start_index = from_index.unwrap_or_default();
244-
assert!(start_index < self.election_counter as u128, "Invalid start index");
243+
assert!(
244+
start_index < self.election_counter as u128,
245+
"Invalid start index"
246+
);
245247
let limit = limit.map(|v| v as usize).unwrap_or(usize::MAX);
246248

247-
self.elections.iter().map(|(_, election)| election.clone()).skip(start_index as usize).take(limit).collect()
249+
self.elections
250+
.iter()
251+
.map(|(_, election)| election.clone())
252+
.skip(start_index as usize)
253+
.take(limit)
254+
.collect()
248255
}
249256

250257
pub fn get_election(&self, election_id: &ElectionId) -> Option<Election> {
@@ -254,15 +261,15 @@ impl Contract {
254261
/// Returns whether an election is currently in the voting period
255262
pub fn is_voting_period(&self, election_id: &ElectionId) -> bool {
256263
self.elections.get(election_id).map_or(false, |election| {
257-
let now = env::block_timestamp();
264+
let now = env::block_timestamp_ms();
258265
now >= election.start_date.0 && now < election.end_date.0
259266
})
260267
}
261268

262269
/// Returns whether an election has ended
263270
pub fn is_election_ended(&self, election_id: &ElectionId) -> bool {
264271
self.elections.get(election_id).map_or(false, |election| {
265-
env::block_timestamp() >= election.end_date.0
272+
env::block_timestamp_ms() >= election.end_date.0
266273
})
267274
}
268275

@@ -277,25 +284,21 @@ impl Contract {
277284

278285
/// Returns all active elections (in nomination or voting period)
279286
pub fn get_active_elections(&self) -> Vec<(ElectionId, Election)> {
280-
let now = env::block_timestamp();
287+
let now = env::block_timestamp_ms();
281288
self.elections
282289
.iter()
283-
.filter(|(_, election)| {
284-
now >= election.start_date.0 && now < election.end_date.0
285-
})
290+
.filter(|(_, election)| now >= election.start_date.0 && now < election.end_date.0)
286291
.map(|(id, election)| (*id, election.clone()))
287292
.collect()
288293
}
289294

290295
/// Returns the current phase of an election
291296
pub fn get_election_phase(&self, election_id: &ElectionId) -> Option<ElectionPhase> {
292297
self.elections.get(election_id).map(|election| {
293-
let now = env::block_timestamp();
298+
let now = env::block_timestamp_ms();
294299
if now < election.start_date.0 {
295300
ElectionPhase::Pending
296-
} else if now < election.end_date.0 {
297-
ElectionPhase::Nomination
298-
} else if now < election.end_date.0 {
301+
} else if now >= election.start_date.0 && now < election.end_date.0 {
299302
ElectionPhase::Voting
300303
} else {
301304
ElectionPhase::Ended
@@ -304,27 +307,27 @@ impl Contract {
304307
}
305308

306309
pub fn get_election_candidates(&self, election_id: ElectionId) -> Vec<Candidate> {
307-
let candidates_map = self.candidates
310+
let candidates_map = self
311+
.candidates
308312
.get(&election_id)
309313
.expect("Election not found");
310-
314+
311315
candidates_map.values().cloned().collect()
312316
}
313317

314318
pub fn get_election_votes(&self, election_id: ElectionId) -> Vec<Vote> {
315-
let votes_map = self.votes
316-
.get(&election_id)
317-
.expect("Election not found");
318-
319-
votes_map.values()
319+
let votes_map = self.votes.get(&election_id).expect("Election not found");
320+
321+
votes_map
322+
.values()
320323
.flat_map(|votes| votes.iter().cloned())
321324
.collect()
322325
}
323326

324327
/// Returns time remaining (in nanoseconds) in the current phase
325328
pub fn get_time_remaining(&self, election_id: &ElectionId) -> Option<u64> {
326329
self.elections.get(election_id).map(|election| {
327-
let now = env::block_timestamp();
330+
let now = env::block_timestamp_ms();
328331
if now < election.start_date.0 {
329332
election.start_date.0 - now
330333
} else if now < election.end_date.0 {

contracts/voting/src/events.rs

-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::*;
22

3-
43
pub const EVENT_JSON_PREFIX: &str = "EVENT_JSON:";
54
/// source metadata update
65
pub(crate) fn log_election_created_event(election: Election, candidates: &Vec<AccountId>) {
@@ -24,7 +23,6 @@ pub(crate) fn log_election_created_event(election: Election, candidates: &Vec<Ac
2423
);
2524
}
2625

27-
2826
pub(crate) fn log_vote_event(election_id: ElectionId, vote: (AccountId, u32)) {
2927
env::log_str(
3028
format!(

0 commit comments

Comments
 (0)