The Logic of Voting Smart Contract | Solidity

Share:
The Logic of Voting Smart Contract | Solidity

I will go through all the logic which is needed to be implemented in the voting contract to make it work correctly.

First I need to say is that any public contract function is defined in the contract but made to be called outside, from other contracts or by blockchain users. It is like an interface for interacting with the smart contract.

This time I will firstly explain in words what we need to do and then build real contract function from this.

One more time I will copy all voting contract code here to make it more comfortable to understand.



pragma solidity 0.5.10;

contract VotingSystem { mapping (address => bool) public voters; mapping (address => bool) public voted;

uint256 public totalVoterRegistered;
uint256 public totalVoted;
uint256 public endOfVoting;

uint16 constant votingPeriod = 1;

bool public isVotingClosed;

address public owner;

constructor () public {
    owner = msg.sender;
    endOfVoting = now + votingPeriod;
}

modifier onlyContractOwner() {
    require(owner == msg.sender, 'Not a contract owner');
    _;
}

modifier ifVoitingGoing() {
    require(!isVotingClosed, 'Voting is closed');
    _;
}

modifier onlyValidVoter() {
    require(voters[msg.sender] == true, 'Not a registered voter');
    _;
}

function registerVoter(address _voter) public onlyContractOwner() ifVoitingGoing() returns(bool success) {
    if (voters[_voter] == false) {
        voters[_voter] = true;
        totalVoterRegistered++;
    }
    return true;
}

function unregisterVoter(address _voter) public onlyContractOwner() ifVoitingGoing() returns(bool success) {
    if(voters[_voter] == true) {
        voters[_voter] = false;
        totalVoterRegistered--;
    }
    return true;
}

function registerVoters(address[] memory _voters) public onlyContractOwner() ifVoitingGoing() returns(bool success) {
    uint256 newRegisteredCount;

    for (uint256 i = 0; i < _voters.length; i++) {
        if (voters[_voters[i]] == false) {
            voters[_voters[i]] = true;
            newRegisteredCount++;
        }
    }
    totalVoterRegistered += newRegisteredCount;
    return true;
}

function voteFor() public ifVoitingGoing() onlyValidVoter() returns(bool success) {
    if(voted[msg.sender] == false) {
        voted[msg.sender] = true;
        totalVoted++;
    }
    return true;
}

function voteAgainst() public ifVoitingGoing() onlyValidVoter() returns(bool success) {
    if(voted[msg.sender] == true) {
        voted[msg.sender] = false;
        totalVoted--;
    }
    return true;
}

function endVoting() public ifVoitingGoing() returns(bool success) {
    require(now >= endOfVoting, 'End date has not come yet');
    isVotingClosed = true;

    return true;
}

function getVotingResult() public view returns(uint256 votingResult) {
    require(isVotingClosed, 'Voting is not ended yet');

    return (100/totalVoterRegistered) * totalVoted;
}

}


Registering voters


Let’s start by giving the possibility to register some voters or make it valid.

We need to create a public function for this. This function needs to have two restrictions. 

  1. It can be called only by the owner of the contract because only the owner can give the ability to vote to some specific address. That’s why we have the modifier ‘onlyContractOwner’ in the function list of modifiers.

  2. The second restriction is the voting period. If voting ended nobody is allowed to add more voters.

It is all we can see in the function definition line


function registerVoter(address _voter) public onlyContractOwner() ifVoitingGoing() returns(bool success) {


If any of these two requirements are not met function will stop working and the network state will be reverted.

The function gets one argument - an address that we need to make a legal voter.


Reducing gas expenses


So all we need to do is to map this address in the hashtable ‘voters’ to value ‘true’.

But it is better to make a bit more complicated structure here.

Instead of making just 


voters[_voter] = true;


We do next 


if (voters[_voter] == false) {
voters[_voter] = true;


It is needed for reducing gas expenses for this function. The thing is if we change storage value from no-zero value to no-zero value in our case from ‘true’ to ‘true’ it will still cost us 20 000 gas.

That’s why we first check whether the value is zero value or not spending 200 gas on it. The boolean value has only two options, so if it no-zero than it is true and there is no need to change it.

Also, we need to check it for whether increasing or not the total number of voters which we do in the next line.


totalVoterRegistered++;


After we have done everything we needed from the function we need to return value ‘true’ which will mean that function is executed completely and correctly as I explained in the previous article.


Incrementing the number of voters


But adding one by one address to ‘voters’ can be not really comfortable so we need to make the possibility to add an array of addresses. Actually, it is the same function but with using a loop for adding lots of voters. The one important thing here is the incrementing total number of voters. Taking into account that we first need to check whether the address is already a voter or not we need to increment inside the loop. But changing storage value ‘totalVotersRegistered’ will cost us 20 000 each. To avoid it we first define memory value ‘newRegisteredCount’ inside a function, change it into the loop and then change storage value only once.


uint256 newRegisteredCount;

for (uint256 i = 0; i < _voters.length; i++) { if (voters[_voters[i]] == false) { voters[_voters[i]] = true; newRegisteredCount++; } } totalVoterRegistered += newRegisteredCount;


The next function is the ability to unregister address in case some mistake happened. Actually, it is the same as registration but we set the value to false and decrement total number. In this function, we could avoid checking whether the value is true or not because changing the boolean value from ‘false’ to ‘false’ would cost us nothing. But we also have to decrement here and we need to check for it.


Giving voters the ability to vote


The next point is giving voters the ability to vote. For this, we define a function ‘voteFor()’. This function has two restrictions, you already know the first one, it is ‘isVotingGoing’ and the second one is a restriction that checks whether the address is a valid voter or not. The system of collecting votes is pretty similar to voter registration. If the voter wants to vote, we need to check whether he/she voted already in order to save gas and increment the total number of votes. And then return ‘true’.

The next function depends on logic, if we want to give users the ability to change opinion we make function ’voteAgainst()’ if not then we need to skip it. I made it in order to show how it can be implemented. Actually nothing new just the same as ‘voteFor()’ but vice-versa.


Ending the voting


The next function is super important to understand. This function ‘endVoting()’ actually ends voting. And the only restriction there is modifier ‘isVotingGoing’, so it can be called by anyone in the network.


function endVoting() public ifVoitingGoing() returns(bool success) {


The thing is function will be executed only if time has come. The time is ‘now’ + ‘votingPeriod’ that we defined at the beginning of the contract. The most important here is to make it possible to call this function for anyone. It makes the owner not responsible for it and makes voting completely fair if the voting is ended so anyone and not only the owner can close it, so there will always be somebody who would want to end voting at the needed time and owner would not have a possibility to cheat anyhow. 


require(now >= endOfVoting, 'End date has not come yet');
isVotingClosed = true;



Getting the voting results


And after ‘isVotingClosed’ is set up to ‘true’ modifier ‘ifVotingGoing’ will pass anybody only to see the results. Access for anything else as voting or adding new voters will be closed.

The last function ‘getVotingResults’ is to show the results. It is only accessible when ‘isVotingClosed’ is ‘true’ - only after voting is closed. 


require(isVotingClosed, 'Voting is not ended yet');


And it shows the percent of the voting.


return (100/totalVoterRegistered) * totalVoted;


After the contract is closed it is impossible to reopen or start it again and the only way is to deploy it once more but it will be the other contract already.



Comments

Scroll to top