E1 - BallotContract.sol
Description
The BallotContract
oversees the entirety of the election process within the RCV system. It is responsible for adding new elections, closing them, and updating voter choices based on ranked selections. The contract maintains structured data about each election, including its candidates, start and end times, and status (open or closed). It also handles voter information, storing their ranked choices for specific elections.
Through this contract, elections can be dynamically managed, allowing for their creation, the recording of votes, and the determination of when they are open for voting or have concluded.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
/* Imports */
import "./IBallotContract.sol";
/* Events */
// Event to record when an election is created
event ElectionCreated(
uint indexed electionId,
string[] candidates,
uint electionEndTime
);
// Event to record when an election is closed
event ElectionClosed(
uint indexed electionId
);
event TheWinnerIs(
string winner
);
/**
* @title BallotContract
* @dev Oversees the entire voting process, interacts with VotingContract to store votes.
* Implements the logic for tallying votes based on RCV.
*/
contract BallotContract is IBallotContract {
/* State Variables */
address private owner;
// Counter to keep track of the number of elections
uint private electionCount = 1;
// Array to store open elections
uint[] public openElections;
// Array to store closed elections
uint[] public closedElections;
// Election Status bool
bool private electionOpen = false;
/* Constructor */
// Set the owner of the contract
constructor() {
owner = msg.sender;
}
/* Structs */
// Struct to store a list of candidates for an election
struct Election {
uint electionId;
string[] candidates;
uint electionStartTime;
uint electionEndTime;
bool electionOpen;
}
// Struct to store a voter's ranked choices
struct VoterChoices {
uint8 firstChoice;
uint8 secondChoice;
uint8 thirdChoice;
bool hasVoted;
}
// Struct to store the vote count for each candidate
struct CandidatesVoteCount {
uint firstChoiceVotes;
uint secondChoiceVotes;
uint thirdChoiceVotes;
}
/* Mappings */
// Mapping of election index to the election
mapping(uint => Election) private elections;
// Mapping of voter address to their ranked choices
mapping(uint=> mapping(address => VoterChoices)) public voterChoices;
// Mapping candidates to their vote count
mapping(uint=> mapping(string => CandidatesVoteCount)) public candidatesVoteCount;
// Mapping of election winner to the election
mapping(uint => string) public electionWinner;
/* Modifiers */
modifier onlyOwner() {
require(msg.sender == owner, "Only the owner can call this function");
_;
}
/* Functions */
/**
* @dev Function to add an election to the contract
* @param _candidates The list of candidates for the election
* @param _timeDays The number of days the election will be open
*/
function addElection(string[] memory _candidates, uint _timeDays) public onlyOwner {
// Require that the election has at least 2 candidates
require(_candidates.length > 1, "There must be more than one candidate");
// Maximum amount of candidates in 5
require (_candidates.length <= 5, "An election can have a maximum of 5 candidates");
// Store the start time of the election
uint electionStartTime = block.timestamp;
// Calculate the end time of the election
uint electionEndTime = block.timestamp + (_timeDays * 1 days);
// Add the election to the list of elections
Election memory newElection = Election(electionCount, _candidates, electionStartTime, electionEndTime, true);
// Create a new CandidatesVoteCount struct for each candidate
CandidatesVoteCount memory newCandidate;
// Add candidates to the candidatesVoteCount mapping
for (uint i = 0; i < _candidates.length; i++) {
newCandidate = CandidatesVoteCount(0, 0, 0);
for (uint j = 0; j < _candidates.length; j++) {
candidatesVoteCount[electionCount][_candidates[i]] = CandidatesVoteCount(0, 0, 0);
}
}
// Record the election in the mapping
elections[electionCount] = newElection;
// Add the election to the list of open elections
openElections.push(electionCount);
// Increment the election count
electionCount += 1;
// Emit an event to record the creation of the election
emit ElectionCreated(newElection.electionId, newElection.candidates, newElection.electionEndTime);
}
/**
* @dev Function to close an election
* @param _electionId The ID of the election to close
*/
function closeElection(uint _electionId) public onlyOwner {
// Require that the election exists
require(_electionId > 0 && _electionId < electionCount, "Election does not exist");
// Require that the election is open
require(elections[_electionId].electionOpen == true, "Election is already closed");
// Get the election
Election storage election = elections[_electionId];
// Check if the current time is past the election end time
if (block.timestamp > election.electionEndTime) {
// If it is, set the election status to closed
election.electionOpen = false;
// Pick winner
string memory winner = pickWinner(_electionId);
// Map the winner to the election
electionWinner[_electionId] = winner;
// Emit the winner
emit TheWinnerIs(winner);
// Emit an event to record the closing of the election
emit ElectionClosed(_electionId);
// Remove the election from the list of open elections
for (uint i = 0; i < openElections.length; i++) {
if (openElections[i] == _electionId) {
openElections[i] = openElections[openElections.length - 1];
openElections.pop();
break;
}
}
// Add the election to the list of closed elections
closedElections.push(_electionId);
} else {
// If it is not, revert the transaction
revert("Election is still open");}
}
// Function to update voter choices
function updateVoterChoices(uint8 _firstChoice, uint8 _secondChoice, uint8 _thirdChoice, uint _electionId, address _voter) external {
// Update the voter's choices
voterChoices[_electionId][_voter].firstChoice = _firstChoice;
voterChoices[_electionId][_voter].secondChoice = _secondChoice;
voterChoices[_electionId][_voter].thirdChoice = _thirdChoice;
voterChoices[_electionId][_voter].hasVoted = true;
}
function addVotesToCount(uint8 _firstChoice, uint8 _secondChoice, uint8 _thirdChoice, uint _electionId) external {
// Get the candidates for the election
string[] memory candidates = elections[_electionId].candidates;
// Get the candidates' vote count
CandidatesVoteCount storage candidateVoteCount = candidatesVoteCount[_electionId][candidates[_firstChoice]];
// Add the votes to the candidates' vote count
candidateVoteCount.firstChoiceVotes += 1;
// Get the candidates' vote count
candidateVoteCount = candidatesVoteCount[_electionId][candidates[_secondChoice]];
// Add the votes to the candidates' vote count
candidateVoteCount.secondChoiceVotes += 1;
// Get the candidates' vote count
candidateVoteCount = candidatesVoteCount[_electionId][candidates[_thirdChoice]];
// Add the votes to the candidates' vote count
candidateVoteCount.thirdChoiceVotes += 1;
}
function pickWinner(uint _electionId) internal view returns (string memory) {
// Get the candidates for the election
string[] memory candidates = elections[_electionId].candidates;
// Get the candidates' vote count
CandidatesVoteCount memory candidateVoteCount = candidatesVoteCount[_electionId][candidates[0]];
// Set the winner to the first candidate
string memory winner = candidates[0];
// Set the winner's vote count
uint winnerVoteCount = candidateVoteCount.firstChoiceVotes;
// Loop through the candidates
for (uint i = 1; i < candidates.length; i++) {
// Get the candidates' vote count
candidateVoteCount = candidatesVoteCount[_electionId][candidates[i]];
// Check if the candidate has more votes than the current winner
if (candidateVoteCount.firstChoiceVotes > winnerVoteCount) {
// If they do, set the winner to the candidate
winner = candidates[i];
// Set the winner's vote count
winnerVoteCount = candidateVoteCount.firstChoiceVotes;
// if the first votes are tied the second choice votes decide the winner
} else if (candidateVoteCount.firstChoiceVotes == winnerVoteCount && candidateVoteCount.secondChoiceVotes > candidatesVoteCount[_electionId][winner].secondChoiceVotes) {
winner = candidates[i];
winnerVoteCount = candidateVoteCount.secondChoiceVotes;
// if the first and second votes are tied the third choice votes decide the winner
} else if (candidateVoteCount.firstChoiceVotes == winnerVoteCount && candidateVoteCount.secondChoiceVotes == candidatesVoteCount[_electionId][winner].secondChoiceVotes && candidateVoteCount.thirdChoiceVotes > candidatesVoteCount[_electionId][winner].thirdChoiceVotes) {
winner = candidates[i];
winnerVoteCount = candidateVoteCount.thirdChoiceVotes;
}
}
return winner;
}
/**
* @dev Function to add votes to the contract
* @param _electionId The ID of the election to add votes to
* @param _votes The list of votes to add
*/
function checkElection(uint8[] memory _votes, uint _electionId) external view{
// require election exists
require(_electionId > 0 && _electionId < electionCount, "Election does not exist");
// require that the election is open
require (elections[_electionId].electionOpen == true, "Election is closed");
// Require the right amount of votes
require (_votes.length == elections[_electionId].candidates.length, "The amount of votes does not match the amount of candidates");
}
/* Getter Functions */
// Function to get the owner of the contract
function getOwner() public view returns (address) {
return owner;
}
// function to get the election count
function getElectionCount() public view returns (uint) {
return electionCount - 1;
}
// Function to get the open elections
function getOpenElections() public view returns (uint[] memory) {
return openElections;
}
// Function to get the closed elections
function getClosedElections() public view returns (uint[] memory) {
return closedElections;
}
// Function to get the details of an election
function getElection(uint _electionId) public view returns (string[] memory, uint, uint, bool) {
return (elections[_electionId].candidates, elections[_electionId].electionStartTime , elections[_electionId].electionEndTime, elections[_electionId].electionOpen);
}
// Function to get the candidates of an election
function getElectionCandidates(uint _electionId) public view returns (string[] memory) {
return elections[_electionId].candidates;
}
// Function to get the status of an election
function getElectionStatus(uint _electionId) external view returns (bool) {
return elections[_electionId].electionOpen;
}
// Function to get the start time of an election
function getElectionStartTime(uint _electionId) public view returns (uint) {
return elections[_electionId].electionStartTime;
}
// Function to get the end time of an election
function getElectionEndTime(uint _electionId) public view returns (uint) {
return elections[_electionId].electionEndTime;
}
// Function to get election winner
function getElectionWinner(uint _electionId) public view returns (string memory) {
return electionWinner[_electionId];
}
// Function to get the vote count for a candidate
function getCandidateVoteCount(uint _electionId, string memory _candidate) public view returns (uint, uint, uint) {
return (
candidatesVoteCount[_electionId][_candidate].firstChoiceVotes,
candidatesVoteCount[_electionId][_candidate].secondChoiceVotes,
candidatesVoteCount[_electionId][_candidate].thirdChoiceVotes
);
}
// Function to get a voter's vote status
function getVoterStatus(address _voter, uint _electionId) public view returns (bool) {
return voterChoices[_electionId][_voter].hasVoted;
}
// Function to get a voter's ranked choices
function getVoterChoices(
address _voter, uint _electionId
) public view returns (uint8, uint8, uint8) {
return (
voterChoices[_electionId][_voter].firstChoice,
voterChoices[_electionId][_voter].secondChoice,
voterChoices[_electionId][_voter].thirdChoice
);
}
}
Last updated