Analysis of Voting Smart Contract | Solidity Code

Share:
Analysis of Voting Smart Contract | Solidity Code

In this article, I am going to shed light on full contract logic from the previous article.

One thing before start: a much deeper understanding will come in the next articles, it is just surface explanation.

You already know about the global variables ‘mapping’ type and the Unix timestamp system.

Ok, let's move to the code.




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 = 9000;

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');
    _;
}

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() returns(bool success) {
    require(voters[msg.sender] == true, 'Not a registered voter');

    if(voted[msg.sender] == false) {
        voted[msg.sender] = true;
        totalVoted++;
    }
    return true;
}

function voteAgainst() public ifVoitingGoing() returns(bool success) {
    require(voters[msg.sender] == true, 'Not a registered voter');

    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;
}

}


Constructor function


Let’s move to the next part. It is a constructor.

The constructor function is one of the special function types in Solidity. It is automatically called at the time the contract is deployed to the system. It gives us the possibility to make some actions actually before the contract starts working. First and one of the most important things that we need to do here is to define the contract owner's address. Usually, the contract owner is like an administrator. In the contract, we often need to define some actions that cannot be done by an ordinary user because it will break the contract logic. That is why we need an address that is able to make functions calls with restricted access. To understand some functions with restricted access we need to get to know one more thing.

In every message call in Solidity, there are special properties. It is some objects that keep specific transaction details. The one that we need right now is the ‘msg’ object.

It has a few parameters. Two of them that we use here are next:


msg.sender

msg.value


msg.sender is an address that sent transaction and made a message call with it. It means that in any contract function we can get the address that called this function and check if this address is an owner or not.

But before we check whether the address is owner or not we need to save the owner's address to storage.

That is why we have the next line of code in the constructor.


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


In this line, we memorize the address that called constructor function to the storage variable ‘owner’. Taking into account that address which deploys the contract is automatically the address which calls constructor, we will store the contract creator address in the variable ‘owner’.

The next stuff that we need to do in the constructor is to set the ‘endOfVoting’ variable. Basically, it is a date of closing the voting. So here variable ‘now’ will be the time of contract creation and ‘votingperiod’ is a predefined constant. 


Modifier type


Now we know how to store needed info and make it ready to use. The only part that is left is actually using it.

The good practice to make some restricting of function calling are the modifiers.

The modifier is a special function type in Solidity. With the help of a modifier, we can put some additional code in any function that we will use modifier with. Let’s take a look at the syntax of modifiers using the example of ‘onlyContractOwner’ modifier.


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


To define the modifier we need to use the keyword ‘modifier’, it looks pretty like a function. We also can pass the arguments to the modifier.

The main difference is a ‘_;’ operator. It shows where the actual function body should be located.

So if we call this modifier with any function the order will be the next.
First-line with ‘require()’ will be called and if it passes function will start executing.

What is ‘require()’?

Require is a special function in Solidity language. Basically, it is an ordinary inspection.

The line 


require(owner == msg.sender, 'Not a contract owner');

Checks if condition inside it is true and if so, passes. Otherwise, it throws and sends a message that is defined as the second parameter.

But not only. Also, the require function reverts all changes which were done to storage during this function. That is extremely important in most cases. If the function had done some changes to storage and had thrown without reverting, changes would have stayed anyway. In this single case, we can avoid reverting, because no changes were made to storage yet. But if you are not sure, it is always better to revert.

Let's now take a look at how to use modifiers in functions. For this, we first need to get to know how to define the function itself.


Functions 


We will take as an example the first function that is defined in the contract.



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

To define a function we need to use the keyword ‘function’ first. Looks familiar to other programming languages. What is not familiar is that we need to place all modifiers after the list of arguments in a function declaration. Here we can use as default Solidity modifiers as our own. The first one ‘public’ means that this function can be called from any place in the Ethereum network. The next two our own modifiers mean that this function can be called only by the contract owner and only while voting is going. If this condition will not be met, the function will be reverted. And the last one ‘returns()’ show which value type is going to be returned from the function.

It is a good practice to return a boolean value from all function that does not return anything else. It is done to make the debugging process easier. To make an illustration, if the function is reverted, it will return ‘false’ because all variables in Solidity are null-values by default and only if the function is executed correctly it will return ‘true’. It is also possible to write just ‘returns(bool)’ without mentioning the name of the variable here which is ‘success’ in our case. But it is also done for better debugging. It is a good practice to specify what exactly you want to return from the function.

In the next article, I’m going to explain how the actual login of the contract works, as you already know the basics of implementation.


Armen Arakelyan



Comments

Scroll to top