@@ -25,15 +25,27 @@ error ElectionDatabase__CandidateNotRegistered();
2525/// @notice Thrown when a candidate is already registered in the election
2626error ElectionDatabase__CandidateAlreadyEnrolled ();
2727
28- /// @notice Thrown when a restricted action is attempted during an active election
29- error ElectionDatabase__ElectionActive ();
30-
31- /// @notice Thrown when an election is not currently accepting votes
32- error ElectionDatabase__ElectionClosed ();
33-
3428/// @notice Thrown when the requested election does not exist
3529error ElectionDatabase__ElectionNotFound ();
3630
31+ /// @notice Thrown when the election status is not new
32+ error ElectionDatabase__ElectionNotNew ();
33+
34+ /// @notice Thrown when a restricted action is attempted during an active election, like enrolling a candidate
35+ error ElectionDatabase__ElectionNotActive ();
36+
37+ /// @notice Thrown when an election has already been completed
38+ error ElectionDatabase__ElectionAlreadyCompleted ();
39+
40+ /// @notice Thrown when an election is not yet completed
41+ error ElectionDatabase__ElectionNotCompleted ();
42+
43+ /// @notice Thrown when trying to perform an action on an archived election
44+ error ElectionDatabase__ElectionAlreadyArchived ();
45+
46+ /// @notice Thrown when trying to complete an election without sufficient data
47+ error ElectionDatabase__CannotCompleteElection ();
48+
3749/// @notice Thrown when an election has no contestants/candidates enrolled
3850error ElectionDatabase__ElectionHasNoContestant ();
3951
@@ -51,6 +63,20 @@ error ElectionDatabase__InvalidAddress();
5163 * - Integration with VoterDatabase and CandidateDatabase
5264 */
5365contract ElectionDatabase is AdminManagement {
66+ /**
67+ * @notice Election status enumeration
68+ * @dev NEW: Election created, candidates can enroll
69+ * @dev ACTIVE: Election open for voting
70+ * @dev COMPLETED: Election closed, results calculated
71+ * @dev ARCHIVED: Election archived, no further modifications allowed
72+ */
73+ enum ElectionStatus {
74+ NEW,
75+ ACTIVE,
76+ COMPLETED,
77+ ARCHIVED
78+ }
79+
5480 /**
5581 * @notice Stores details for a single election
5682 * @dev The registrationTimestamp serves as both a timestamp and a registration flag
@@ -67,8 +93,8 @@ contract ElectionDatabase is AdminManagement {
6793 mapping (address => address ) voterToChosenCandidate;
6894 // voter -> timestamp when they voted in this specific election (0 if not voted)
6995 mapping (address => uint256 ) voterToVoteTimestamp;
70- // used to track whether the election is active or not
71- bool isActive ;
96+ // used to track the current status of the election
97+ ElectionStatus status ;
7298 uint256 totalVotes;
7399 // If > 0, election is registered. Acts as creation timestamp
74100 uint256 registrationTimestamp;
@@ -140,8 +166,11 @@ contract ElectionDatabase is AdminManagement {
140166 /// @notice Emitted when an election is opened for voting
141167 event ElectionOpened (uint256 indexed electionId , address indexed admin );
142168
143- /// @notice Emitted when an election is closed
144- event ElectionClosed (uint256 indexed electionId , address indexed admin );
169+ /// @notice Emitted when an election is completed
170+ event ElectionCompleted (uint256 indexed electionId , address indexed admin );
171+
172+ /// @notice Emitted when an election is archived
173+ event ElectionArchived (uint256 indexed electionId , address indexed admin );
145174
146175 /**
147176 * @notice Ensures the election exists
@@ -153,23 +182,43 @@ contract ElectionDatabase is AdminManagement {
153182 _;
154183 }
155184
185+ /**
186+ * @notice Ensures the election is in new state
187+ * @param _electionId ID of the election to check
188+ */
189+ modifier onlyNewElection (uint256 _electionId ) {
190+ if (s_elections[_electionId].status != ElectionStatus.NEW)
191+ revert ElectionDatabase__ElectionNotNew ();
192+ _;
193+ }
194+
156195 /**
157196 * @notice Ensures the election is open for voting
158197 * @param _electionId ID of the election to check
159198 */
160- modifier onlyOpenElection (uint256 _electionId ) {
161- if (! s_elections[_electionId].isActive )
162- revert ElectionDatabase__ElectionClosed ();
199+ modifier onlyActiveElection (uint256 _electionId ) {
200+ if (s_elections[_electionId].status != ElectionStatus.ACTIVE )
201+ revert ElectionDatabase__ElectionNotActive ();
163202 _;
164203 }
165204
166205 /**
167- * @notice Ensures the election is closed/inactive
206+ * @notice Ensures the election is not archived
168207 * @param _electionId ID of the election to check
169208 */
170- modifier onlyClosedElection (uint256 _electionId ) {
171- if (s_elections[_electionId].isActive)
172- revert ElectionDatabase__ElectionActive ();
209+ modifier onlyNonArchivedElection (uint256 _electionId ) {
210+ if (s_elections[_electionId].status == ElectionStatus.ARCHIVED)
211+ revert ElectionDatabase__ElectionAlreadyArchived ();
212+ _;
213+ }
214+
215+ /**
216+ * @notice Ensures the election is completed
217+ * @param _electionId ID of the election to check
218+ */
219+ modifier onlyCompletedElection (uint256 _electionId ) {
220+ if (s_elections[_electionId].status != ElectionStatus.COMPLETED)
221+ revert ElectionDatabase__ElectionNotCompleted ();
173222 _;
174223 }
175224
@@ -241,7 +290,7 @@ contract ElectionDatabase is AdminManagement {
241290 Election storage newElection = s_elections[electionId];
242291 newElection.name = _name;
243292 newElection.description = _description;
244- newElection.isActive = false ;
293+ newElection.status = ElectionStatus.NEW ;
245294 newElection.totalVotes = 0 ;
246295 newElection.registrationTimestamp = block .timestamp ; // Set timestamp to register the election
247296
@@ -262,7 +311,12 @@ contract ElectionDatabase is AdminManagement {
262311 uint256 _electionId ,
263312 string memory _name ,
264313 string memory _description
265- ) external onlyAdmin onlyRegisteredElection (_electionId) {
314+ )
315+ external
316+ onlyAdmin
317+ onlyRegisteredElection (_electionId)
318+ onlyNonArchivedElection (_electionId)
319+ {
266320 Election storage election = s_elections[_electionId];
267321
268322 election.name = _name;
@@ -305,43 +359,80 @@ contract ElectionDatabase is AdminManagement {
305359 */
306360 function adminOpenElection (
307361 uint256 _electionId
308- ) external onlyAdmin onlyRegisteredElection (_electionId) {
362+ )
363+ external
364+ onlyAdmin
365+ onlyRegisteredElection (_electionId)
366+ onlyNewElection (_electionId)
367+ {
309368 Election storage election = s_elections[_electionId];
310369
311370 // Prevent opening elections with no candidates
312371 if (election.candidates.length == 0 ) {
313372 revert ElectionDatabase__ElectionHasNoContestant ();
314373 }
315374
316- election.isActive = true ;
375+ election.status = ElectionStatus.ACTIVE ;
317376 emit ElectionOpened (_electionId, msg .sender );
318377 }
319378
320379 /**
321- * @notice Closes an election from voting
380+ * @notice Completes an election and calculates results
381+ * @dev Only owner/admins can call this function
382+ * @dev Election must be active and have candidates enrolled and votes cast
383+ * @param _electionId ID of the election to complete
384+ */
385+ function adminCompleteElection (
386+ uint256 _electionId
387+ )
388+ external
389+ onlyAdmin
390+ onlyRegisteredElection (_electionId)
391+ onlyActiveElection (_electionId)
392+ {
393+ Election storage election = s_elections[_electionId];
394+
395+ // Ensure election has candidates and votes to calculate a winner
396+ if (election.candidates.length == 0 || election.totalVotes == 0 ) {
397+ revert ElectionDatabase__CannotCompleteElection ();
398+ }
399+
400+ election.status = ElectionStatus.COMPLETED;
401+ emit ElectionCompleted (_electionId, msg .sender );
402+ }
403+
404+ /**
405+ * @notice Archives an election
322406 * @dev Only owner/admins can call this function
323- * @param _electionId ID of the election to close
407+ * @dev Only completed elections can be archived
408+ * @param _electionId ID of the election to archive
324409 */
325- function adminCloseElection (
410+ function adminArchiveElection (
326411 uint256 _electionId
327412 ) external onlyAdmin onlyRegisteredElection (_electionId) {
328413 Election storage election = s_elections[_electionId];
329- election.isActive = false ;
330- emit ElectionClosed (_electionId, msg .sender );
414+ if (election.status == ElectionStatus.ARCHIVED) {
415+ revert ElectionDatabase__ElectionAlreadyArchived ();
416+ }
417+ if (election.status == ElectionStatus.COMPLETED) {
418+ revert ElectionDatabase__ElectionAlreadyCompleted ();
419+ }
420+ election.status = ElectionStatus.ARCHIVED;
421+ emit ElectionArchived (_electionId, msg .sender );
331422 }
332423
333424 /**
334425 * @notice Allows a candidate to enroll themselves in an election
335426 * @dev Candidate must be registered in CandidateDatabase
336- * @dev Election must be in closed state
427+ * @dev Election must be in pending state
337428 * @param _electionId ID of the election to enroll in
338429 */
339430 function enrollCandidate (
340431 uint256 _electionId
341432 )
342433 external
343434 onlyRegisteredElection (_electionId)
344- onlyClosedElection (_electionId)
435+ onlyNewElection (_electionId)
345436 onlyRegisteredCandidate (msg .sender )
346437 {
347438 Election storage election = s_elections[_electionId];
@@ -360,15 +451,15 @@ contract ElectionDatabase is AdminManagement {
360451
361452 /**
362453 * @notice Allows a candidate to withdraw themselves from an election
363- * @dev Election must be in closed state
454+ * @dev Election must be in pending state
364455 * @param _electionId ID of the election to withdraw from
365456 */
366457 function withdrawCandidate (
367458 uint256 _electionId
368459 )
369460 external
370461 onlyRegisteredElection (_electionId)
371- onlyClosedElection (_electionId)
462+ onlyNewElection (_electionId)
372463 {
373464 Election storage election = s_elections[_electionId];
374465
@@ -404,7 +495,7 @@ contract ElectionDatabase is AdminManagement {
404495 )
405496 external
406497 onlyRegisteredElection (_electionId)
407- onlyOpenElection (_electionId)
498+ onlyActiveElection (_electionId)
408499 onlyEnrolledCandidate (_electionId, _candidate)
409500 onlyRegisteredVoter
410501 {
@@ -439,6 +530,7 @@ contract ElectionDatabase is AdminManagement {
439530 external
440531 onlyAdmin
441532 onlyRegisteredElection (_electionId)
533+ onlyNonArchivedElection (_electionId)
442534 onlyRegisteredCandidate (_candidate)
443535 {
444536 Election storage election = s_elections[_electionId];
@@ -464,7 +556,12 @@ contract ElectionDatabase is AdminManagement {
464556 function adminWithdrawCandidate (
465557 uint256 _electionId ,
466558 address _candidate
467- ) external onlyAdmin onlyRegisteredElection (_electionId) {
559+ )
560+ external
561+ onlyAdmin
562+ onlyRegisteredElection (_electionId)
563+ onlyNonArchivedElection (_electionId)
564+ {
468565 Election storage election = s_elections[_electionId];
469566
470567 // Find and remove the candidate
@@ -502,22 +599,27 @@ contract ElectionDatabase is AdminManagement {
502599 }
503600
504601 /**
505- * @notice Returns whether the election is currently active
602+ * @notice Returns the current status of the election
506603 * @param _electionId ID of the election
507- * @return Status of the election (true if active)
604+ * @return Status of the election
508605 */
509606 function getElectionStatus (
510607 uint256 _electionId
511- ) external view onlyRegisteredElection (_electionId) returns (bool ) {
512- return s_elections[_electionId].isActive;
608+ )
609+ external
610+ view
611+ onlyRegisteredElection (_electionId)
612+ returns (ElectionStatus)
613+ {
614+ return s_elections[_electionId].status;
513615 }
514616
515617 /**
516618 * @notice Returns comprehensive details of an election
517619 * @param _electionId ID of the election
518620 * @return name Name of the election
519621 * @return description Description of the election
520- * @return isActive Whether the election is currently active
622+ * @return status Current status of the election
521623 * @return candidates Array of candidate addresses enrolled in the election
522624 * @return totalVotes Total number of votes cast in the election
523625 * @return registrationTimestamp When the election was created
@@ -531,7 +633,7 @@ contract ElectionDatabase is AdminManagement {
531633 returns (
532634 string memory name ,
533635 string memory description ,
534- bool isActive ,
636+ ElectionStatus status ,
535637 address [] memory candidates ,
536638 uint256 totalVotes ,
537639 uint256 registrationTimestamp
@@ -541,7 +643,7 @@ contract ElectionDatabase is AdminManagement {
541643 return (
542644 election.name,
543645 election.description,
544- election.isActive ,
646+ election.status ,
545647 election.candidates,
546648 election.totalVotes,
547649 election.registrationTimestamp
@@ -598,7 +700,13 @@ contract ElectionDatabase is AdminManagement {
598700 */
599701 function getWinner (
600702 uint256 _electionId
601- ) external view onlyRegisteredElection (_electionId) returns (address ) {
703+ )
704+ external
705+ view
706+ onlyRegisteredElection (_electionId)
707+ onlyCompletedElection (_electionId)
708+ returns (address )
709+ {
602710 Election storage election = s_elections[_electionId];
603711 uint256 maxVotes = 0 ;
604712 address winnerAddress = address (0 );
0 commit comments