Voting Smart Contract | Smart Contract Project #2

Mr. ΰ€‹
15 min readMar 6, 2023

--

πŸ‘‰πŸ‘‰πŸ‘‰πŸ‘‰πŸ‘‰πŸ‘‰Stay connected by following us!πŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆπŸ‘ˆ
⭐ Check out our source at: https://rootbabu.github.io/solidity
🏠 Join our vibrant community on Discord: https://discord.gg/UqrZ78gYg3
πŸ‘‰πŸ‘‰πŸ‘‰ Together, let’s explore the exciting world of web3! πŸ‘ˆπŸ‘ˆπŸ‘ˆ

Voting is a fundamental aspect of democracy, and it is essential for decision-making in any organization. With the advent of blockchain technology, it is now possible to create decentralized voting systems that are transparent, secure, and tamper-proof.

Goals and Objectives

The objective of the Voting Smart Contract is to establish a decentralized and fair voting system. The contract aims to accomplish the following goals:

  • Enable the public to create associations on the contract. For example, three associations can be created, named Avengers, Sunrisers, and Thunderbirds. Associations are groups of individuals or entities with a common goal or interest.
  • Allow voters to take part in the voting process by casting their votes for the above-mentioned associations. Certain conditions apply, such as voters being able to vote only when the voting is open and only being able to vote once and for existing associations.
  • Allow the public to check the total number of votes cast.
  • Allow the owner to end the voting and announce the association that received the most votes as the winner.

Code Implementation

The first step in creating a voting smart contract is to define the structure of the contract. In our example, we will be creating a contract for voting for different associations. The contract will have a struct called Association that will hold the name of the association and the number of votes it has received.

struct Association {
string name; // name of the association
uint votes; // number of votes received by the association
}

In the next step, we will establish an array known as associations to store all the associations that are eligible for voting. Furthermore, we will create an array known as voters to keep track of the addresses of the voters and ensure that each voter can only cast one vote. This array is designated as private, which means that it cannot be accessed by anyone other than the smart contract itself.

Association[] public associations; // array of all associations
address[] private voters; // list of addresses of voters

We will also establish a flag named votingOpen to indicate whether the voting is currently open or closed. Upon deployment of the contract, the votingOpen flag will be set to true, signifying that voting is open. Once the owner ends the voting process, the votingOpen flag will be changed to false, indicating that voting is closed. This variable is designated as public, which means that anyone can check the status of the voting process at any time.

bool public votingOpen; // flag to check if voting is open or closed

The owner variable stores the address of the user who deployed the contract and has the permission to close the voting.

address public owner; // address of the owner of the contract

The winner variable stores the name of the association that received the most votes. This variable is marked as public so that anyone can check who is winner after end of the vote.

string public winner; // name of the association with the most votes

The next step is to create the constructor function. The constructor function is executed when the contract is deployed and sets the owner variable to the address of the user who deployed the contract and sets the votingOpen flag to true.

constructor() {
owner = msg.sender; // set the owner of the contract as the msg.sender
votingOpen = true; // set votingOpen flag to true
}

We will also create a function called addAssociation that will allow users to add a new association to the contract. This function takes in a single parameter, _name, which is the name of the association being added. The function starts by checking if an association with the same name already exists using the associationExists function. If an association with the same name already exists, the function throws an error message "Association with this name already exists.". If the association name is unique, the function then creates a new association using the given name and sets the number of votes to 0.

The new association is then added to the associations array which stores all the associations. This allows the contract to keep track of all the associations that have been added and their vote counts. This function is useful for anybody to add new associations to the contract for voting.

function addAssociation(string memory _name) public {
// check if an association with the same name already exists
require(!associationExists(_name), "Association with this name already exists.");
// create a new association with the given name and 0 votes
Association memory newAssociation = Association(_name, 0);
// add the new association to the list of associations
associations.push(newAassociation);
}

The next step is to create the associationExists function, which will be invoked from the addAssociation function, and it will verify if an association with a specific name already exists within the contract. This function is used to check if an association with a specific name already exists in the smart contract by comparing the input name with the names of all associations stored in the contract and returning true if a match is found or false otherwise. The purpose of this function is to ensure that only unique association names are added to the contract.

function associationExists(string memory _name) private view returns (bool) {
// loop through all existing associations
for (uint i = 0; i < associations.length; i++) {
// hash the name of the current association and the searched name
bytes4 hashedString = bytes4(keccak256(abi.encodePacked(associations[i].name)));
bytes4 hashedSearchString = bytes4(keccak256(abi.encodePacked(_name)));
// check if the hashed name of the current association matches the hashed searched name
if (hashedString == hashedSearchString) {
return true; // association with the same name already exists
}
}
return false; // association with the given name does not exist
}

It starts by taking an input, a string variable named _name, which is the name of the association that we want to check for. This input is passed to the function when it is called.

The function then loops through an array called associations, which contains all the associations that have been created. For each association in the array, the function takes the name of the association and uses the keccak256 function to hash it. This function is a commonly used cryptographic hash function and it is used to create a unique fixed-size output (in this case, a bytes4) from any input.

The function then takes the input string _name and hashes it as well, using the same keccak256 function. It then compares the hashed version of the input name with the hashed version of the current association name in the loop.

Note: In Solidity, strings cannot be compared directly using the usual comparison operators (e.g. ==, !=, >, <, etc.). Instead, one way to compare strings is by using the keccak256() function to hash the strings and then comparing the resulting hash values.

If the two hashed values match, it means that an association with the same name as the input already exists, so the function returns true. If the loop finishes running without finding a match, it means that no association with the same name exists, and the function returns false.

The next step is to create the vote function which allows users to vote for an association. This function takes in an integer called _index which is the index of the association that the user wants to vote for. It checks if voting is open, if the selected association is valid and if the user has already voted. If all these conditions are met, it adds the user's address to the voters array and increases the vote count for the selected association.

function vote(uint _index) public {
// Check if voting is open. If not, throw error message
require(votingOpen, "Voting is closed.");
// Check if the selected association index is within the range of the associations array. If not, throw error message.
require(_index < associations.length, "Invalid association selected.");
// Check if the voter has already voted. If so, throw error message.
require(!voterExists(msg.sender), "You have already voted.");
// Add the voter's address to the voters array
voters.push(msg.sender);
// Increase the vote count for the selected association
associations[_index].votes++;
}

The next step is to create the voterExists function, which is invoked from the vote function, it is used to determine if an individual voter has already casted a vote. The function scans through the voters array and examines if the address of the voter (msg.sender) is present in the array. The for loop is used for this process. The loop starts at index 0 and continues through all the indexes of the array until it reaches the end of the voters array. Within the loop, it checks if the address of the current voter (indicated by "voters[i]") is equal to the address of the current message sender(indicated by "msg.sender"). If the voter's address is found, the function returns true, indicating that the voter has already voted. If it is not found, the function returns false, indicating that the voter has not yet voted.

function voterExists() private view returns (bool) {
// Iterate through the voters array to check if the voter has already voted
for (uint i = 0; i < voters.length; i++) {
if (voters[i] == msg.sender) {
return true;
}
}
// If the voter's address is not found in the voters array, return false
return false;
}

The next step is to implement the getTotalVotes function, which will return the total number of votes cast in the current voting process. The function does this by iterating through all the associations and adding their respective vote counts to a variable named countVotes. The function then returns the final total number of votes.

function getTotalVotes() public view returns (uint) {
// variable to store the total number of votes
uint countVotes = 0;
// loop through all the associations and add their number of votes to the countVotes variable
for (uint i = 0; i < associations.length; i++) {
countVotes += associations[i].votes;
}
// return the total number of votes
return countVotes;
}

The next step is to create the endVoting function, which allows the owner of the contract to close the voting process. This function first checks if the message sender (msg.sender) is the owner of the contract using an require statement. If the message sender is not the owner, the function will throw an error message "Only the owner can close the voting.". Then it checks if the voting is currently open using another require statement with votingOpen flag. If the voting is already closed, it throws an error message "Voting is already closed.". If both the above conditions are met, the function sets the votingOpen flag to false, which indicates that the voting process is closed. Finally, it calls the getWinner function which selects the association with the highest number of votes as the winner of the voting process.

function endVoting() public {
// Check if the msg.sender is the owner of the contract
require(owner == msg.sender, "Only the owner can close the voting.");
// Check if voting is open
require(votingOpen, "Voting is already closed.");
// Set votingOpen flag to false
votingOpen = false;
// Get the winner of the voting
winner = getWinner();
}

Finally, The function getWinner is a private view function which returns the name of the association with the most votes. The function is declared as private, meaning that it can only be called within the smart contract, and view, meaning that it does not modify the state of the contract.

function getWinner() private view returns(string memory){
// Initialize variables to store the max votes and index of the association with max votes
uint maxVotes = 0;
uint maxIndex;
// Iterate through all the associations
for(uint i=0; i<associations.length; i++){
// Check if the current association has more votes than the previous max
if(associations[i].votes > maxVotes){
// Update maxVotes and maxIndex
maxVotes = associations[i].votes;
maxIndex = i;
}
}
// Return the name of the association with the most votes
return associations[maxIndex].name;
}

The function starts by initializing two variables, maxVotes and maxIndex to store the number of votes and index of the association with the most votes. It then iterates through all the association in the associations array. For each association, it checks if the number of votes for that association is greater than the previous max number of votes. If it is, the function updates the maxVotes and maxIndex variables with the number of votes and index of that association.

After the loop, it returns the name of the association with the most votes by using the maxIndex variable to access the correct association in the associations array. This function allows the smart contract to determine which association has the most votes, and to return the name of that association.

Full Code Snippet

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.17;

contract Voting {
struct Association {
string name; // name of the association
uint votes; // number of votes received by the association
}
Association[] public associations; // array of all associations
address[] private voters; // list of addresses of voters
bool public votingOpen; // flag to check if voting is open or closed
address public owner; // address of the owner of the contract
string winner; // name of the association with the most votes
constructor() {
owner = msg.sender; // set the owner of the contract as the msg.sender
votingOpen = true; // set votingOpen flag to true
}
// function to add a new association to the contract
function addAssociation(string memory _name) public {
// check if an association with the same name already exists
require(!associationExists(_name), "Association with this name already exists.");
// create a new association with the given name and 0 votes
Association memory association = Association(_name, 0);
// add the new association to the list of associations
associations.push(association);
}
// function to check if an association with the given name already exists
function associationExists(string memory _name) private view returns (bool) {
// loop through all existing associations
for (uint i = 0; i < associations.length; i++) {
// hash the name of the current association and the searched name
bytes4 hashedString = bytes4(keccak256(abi.encodePacked(associations[i].name)));
bytes4 hashedSearchString = bytes4(keccak256(abi.encodePacked(_name)));
// check if the hashed name of the current association matches the hashed searched name
if (hashedString == hashedSearchString) {
return true; // association with the same name already exists
}
}
return false; // association with the given name does not exist
}
function vote(uint _index) public {
// Check if voting is open. If not, throw error message
require(votingOpen, "Voting is closed.");
// Check if the selected association index is within the range of the associations array. If not, throw error message.
require(_index < associations.length, "Invalid association selected.");
// Check if the voter has already voted. If so, throw error message.
require(!voterExists(), "You have already voted.");
// Add the voter's address to the voters array
voters.push(msg.sender);
// Increase the vote count for the selected association
associations[_index].votes++;
}
function voterExists() private view returns (bool) {
// Iterate through the voters array to check if the voter has already voted
for (uint i = 0; i < voters.length; i++) {
if (voters[i] == msg.sender) {
return true;
}
}
// If the voter's address is not found in the voters array, return false
return false;
}

function getTotalVotes() public view returns (uint) {
// variable to store the total number of votes
uint countVotes = 0;
// loop through all the associations and add their number of votes to the countVotes variable
for (uint i = 0; i < associations.length; i++) {
countVotes += associations[i].votes;
}
// return the total number of votes
return countVotes;
}
function endVoting() public {
// Check if the msg.sender is the owner of the contract
require(owner == msg.sender, "Only the owner can close the voting.");
// Check if voting is open
require(votingOpen, "Voting is already closed.");
// Set votingOpen flag to false
votingOpen = false;
// Get the winner of the voting
winner = getWinner();
}

function getWinner() private view returns(string memory){
// Initialize variables to store the max votes and index of the association with max votes
uint maxVotes = 0;
uint maxIndex;
// Iterate through all the associations
for(uint i=0; i<associations.length; i++){
// Check if the current association has more votes than the previous max
if(associations[i].votes > maxVotes){
// Update maxVotes and maxIndex
maxVotes = associations[i].votes;
maxIndex = i;
}
}
// Return the name of the association with the most votes
return associations[maxIndex].name;
}
}

Output

Accounts

The contract has been deployed using the first account(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), making it the owner of the contract. The owner has the ability to add associations, so we are initially adding three associations named Avengers, Sunrisers, and Thunderbirds. Other individuals can also add more associations, but for now, we are adding them through the owner account. Other accounts will be used as voters.

Output

Voters can cast their vote by using the vote input field and selecting an association by its index number. The function "getTotalVotes" can be used to check the total number of votes cast. The status of the voting process can also be checked by looking at the "votingOpen" flag, which is currently set to true indicating that voting is open.

Error

If an individual attempts to vote multiple times, the contract will not allow it and will throw an error message.

Winner

Once the voting is complete, the owner can end the voting process. After ending the voting process, the "winner" variable can be checked to see which association received the most votes.

Conclusion

The use of smart contracts for voting systems is a rapidly growing area of interest and has the potential to revolutionize the way we vote. Smart contracts provide a number of benefits for voting systems, such as increased transparency, security, and trust.

One real-world example of a voting system using smart contracts is the West Virginia Secretary of State’s office, which in 2018, piloted a mobile voting platform for overseas military personnel, which used blockchain technology and a smart contract-based system for casting and counting votes. The system was built on the Ethereum blockchain, and the smart contract was used to securely manage voter identities and ensure that each vote was counted only once. It is still a new area and the technology is still evolving, but the potential for more widespread adoption in the future is high.

This tutorial has provided a comprehensive guide on how to create a voting smart contract in Solidity. We have covered the basics of smart contracts, the key features a voting contract should have, and the steps needed to implement it. By following this tutorial, you should now have a good understanding of how to create your own voting smart contract and be well on your way to creating your own decentralized voting system.

Important Points:

1. Why direct comparison of strings is not possible in Solidity.

Strings are stored as arrays of bytes and cannot be directly compared to other strings. Instead, the bytes of the two strings must be compared element by element. Additionally, in order to compare strings in Solidity, it is necessary to use a comparison function, such as keccak256() or sha3(), to generate a hash of the string and compare the hash values.

2. In the above Solidity function, the getTotalVotes() function is used to retrieve the total number of votes cast. This is done by initializing a variable countVotes to 0 and then looping through all the associations (or candidates) and adding their number of votes to the countVotes variable.

Instead of doing this, you could also create a state variable countVotes that is incremented each time a vote is cast. This would eliminate the need for the loop in the getTotalVotes() function and would also make it more efficient as the total number of votes would always be accessible as a single variable rather than having to loop through the entire array of associations.

🌟 Upcoming Project: Auction Smart Contract

Community and resources:

Welcome! We invite all beginners to join us in learning more about Solidity Programming language. Our community is a great place to start if you’re new to it, or even if you’re just looking to brush up on your skills. We offer resources and support to help you learn and grow, so please don’t hesitate to join us!

πŸ‘‘DISCORD SERVER: A platform for people to connect and share their Solidity experiences, resources, and support with each other.

🧰ROOTBABU.SOL: A comprehensive overview of the solidity programming language and to collect all relevant resources in one place, such as vulnerabilities and their POCs or writeups, blogs, and cheatsheets.

We encourage you to take advantage of our resources and join our community to learn and grow together. Thank you!

--

--

Mr. ΰ€‹
Mr. ΰ€‹

Written by Mr. ΰ€‹

Software Engineer | Blockchain Enthusiast | Bug Bounty Hunter

No responses yet