Developing a Voting Smart Contract in Solidity using Remix IDE | Blockchain Tutorials
// SPDX-License-Identifier: MIT
Next we will add the compiler version. This will tell the IDE that on which compiler version we need to compile our code on. Depending on your code or code components you can change your compiler version.
pragma solidity ^0.8.0;
Now we will create a new contract. If you are familiar with object oriented programming, it is like declaring a new class. So we will declare a new contract using contract keyword and a name for our contract. Further complete code we will be adding inside this contract.
contract Voting {
}
Now using the struct keyword we will be creating a user defined datatype named Voter. This will be used to store the information about individual voter. Information like whether or not the voter has voted. We have used boolean variable for that. And another information about who has he voted for. For that we will be using uint variable.
struct Voter {
bool hasVoted;
uint256 votedProposalId;
}
Next again by using the struct keyword we will create another user defined datatype named Candidate. First struct was used to store voter information, this will be used to store candidate info, candidates who are participating in the election. We are using string variable to store candidate name and uint variable to store the votes they have received.
struct Candidate {
string name;
uint256 voteCount;
}
Next we declare few state variables that we will be using throughout the contract. First is the admin variable of address datatype to store the address of admin, the node that deploys the smart contract. Remaining two uint variables are to store the start time and end time for the voting, as voting will not be always on. It will be ongoing in certain time period, defined by these two variables
address admin;
uint256 votingStartTime;
uint256 votingEndTime;
Next we create a mapping variable voters. Mapping is like a collection of key value pairs, like dictionary in python. Each data element stored in it will be stored in key value pair. In this voters variable, key is address and value is Voter struct that we created previously. This way we are storing data of multiple voters.
mapping(address => Voter) public voters;
Next we are creating the array of Candidates. Candidates is also a struct that we defined previously. Here we create the array variable proposal which will store the data of all candidates
Candidate[] public proposals;
Now we sometime need to log the data, that is flowing through the program. To do so we will use events, using them we can create and publish the log data. Here we declare two events one is Voted and another is ProposalAdded. Voted has parameters voter address and proposalId. This event will be used to log when voter casts a vote. ProposalAdded also takes two parameters, proposalID and name, this will be used when a new candidate is added to the candidate list.
event Voted(address indexed voter, uint256 indexed proposalId);
event ProposalAdded(uint256 indexed proposalId, string name);
We now create a modifier named onlyAdmin. Modifiers are used to change or modify the behaviour of a function in solidity. Modifier onlyAdmin will be assigned to the functions which we need to provide only admin access. Any other node other than admin cannot call that particular function.
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can call this function");
_;
}
This hasNotVoted modifier will be used to check if a node has already done the voting and trying to vote twice. If a node has already voted and tries to vote again, it will note get executed and the require will throw an error "You have already voted"
modifier hasNotVoted() {
require(!voters[msg.sender].hasVoted, "You have already voted");
_;
}
Modifier validProposal will be used to vote function. It takes the proposalID entered by the node for voting as a parameter. If there are 2 candidates and a node by mistake enters proposalID as 3, we cannot allow that and provide an error "Invalid proposal ID"
modifier validProposal(uint256 proposalId) {
require(proposalId < proposals.length, "Invalid proposal ID");
_;
}
And this will be our last modifier. This will also be used with vote function. This will ensure that everyone will vote after the voting start time and before voting end time. Not before that and not after that.
modifier onlyDuringVotingPeriod() {
require(block.timestamp >= votingStartTime && block.timestamp <= votingEndTime, "Voting is not allowed at this time");
_;
}
Then we define the constructor. In the constructor we have called the msg.sender method. By using this method we can retrieve the address of the node that has deployed the contract. In short we will retrieve and store the admin address in the admin variable for further use.
constructor() {
admin = msg.sender;
}
Now we declare our first function, used to create list of candidates who will participate in the election. It takes three parameters, two will be used to get the candidate names and the third is used to get the duration for voting. We will add name and set vote count to zero and push this element in the proposals array. Same thing will be done again for the second candidate. Then the time at which the function will be called that current time will be set as voting start time. And by using the duration we will calculate the voting end time. We have used onlyAdmin modifier here so that only admin can add candidates and start the voting.
function createVoterList(string memory _name1, string memory _name2,uint256 _votingDurationMinutes) public onlyAdmin {
proposals.push(Candidate({name: _name1, voteCount: 0}));
proposals.push(Candidate({name: _name2, voteCount: 0}));
votingStartTime = block.timestamp;
votingEndTime = votingStartTime + (_votingDurationMinutes * 1 minutes);
}
Next function that we have is vote, which will be used to cast a vote. It will take a single parameter, proposalID of the candidate who you need to vote for. We have used 3 modifiers over here, hasNotVoted will check that node has not voted previously, onlyDuringVotingPeriod will check if voting period is still on and validProposal will check the proposalID entered by the node is a valid one. Once all these checks are done, it will enter the function. If it enters than voter is ready to vote, voters hasVoted boolean variable will be updated to true and votedProposalId will store the id of the candidate he voted for. We will also update the vote count of the candidate having that proposalID. And emit will be used to log the event Voted.
function vote(uint256 proposalId) external hasNotVoted onlyDuringVotingPeriod validProposal(proposalId) {
voters[msg.sender].hasVoted = true;
voters[msg.sender].votedProposalId = proposalId;
proposals[proposalId].voteCount++;
emit Voted(msg.sender, proposalId);
}
Now we create a function getProposal. So if voter wants to check the details of candidate using the proposalID it can be done using this method. Voter will enter the proposalID and name of the candidate will be displayed. So that the voter will be assured who he is voting for.
function getProposal(uint256 proposalId) external view validProposal(proposalId) returns (string memory, uint256) {
Candidate memory proposal = proposals[proposalId];
return (proposal.name);
}
And the last function hasVotingEnded will be used to check if voting is ongoing or if it has ended. So a voter can check beforehand if voting is ongoing and then vote.
function hasVotingEnded() public view returns (bool) {
return block.timestamp > votingEndTime;
}
So this is all the code for our smart contract. We worked with contract, modifier, require, mapping, struct, function and many other solidity concepts. You can add more code to this to enhance the functionalities or to add new functionalities to the application.
Comments
Post a Comment