Voting on a Blockchain: Solidity Contract Codes explained

This is the 2nd of 5 articles to explore the development of an end-to-end Balloting system on Ethereum. In this article, i will walk through the codes the Balloting system’s Smart Contract, Ballot.sol. Do check back often as I work on them.

1. Voting on a Blockchain: How it works
2. Voting on a Blockchain: Solidity Contract Codes explained
3. Voting on a Blockchain: DApp Demonstration
4. Voting on a Blockchain: Ballot Management DApp Code Walk-through
5. Voting on a Blockchain: Voting DApp Code Walk-through

Variables of the Contract 

    struct vote{
        address voterAddress;
        bool choice;
    }

Lines 12 to 15: A vote comprises 2 parts, the wallet address of the voter, and the choice he makes, and aye or a nah. An aye is represented by the value TRUE and a nah, by the value FALSE.

    struct voter{
        string voterName;
        bool voted;
    }

Lines 17 to 20: A voter has 3 attributes, his name, and whether or not he has voted.

    mapping(uint => vote) private votes;
    mapping(address => voter) public voterRegister;

Lines 30 and 31: votes are stored in a mapping named votes and voter are stored in a mapping called voterRegister. Notice that votes are private so that voters cannot read them directly but voterRegister is public so anyone can check what are the eligible voters. One may ask why I wouldn’t store my votes and voters in arrays. The challenge with using arrays in an Ethereum blockchain is that you are likely to start running out of gas while trying to traverse the records of a considerably sized array. 

    uint private countResult = 0;

Line 22: countResult is a private variable that no one can see. It tells you the total number of votes that said Aye. When the contract is first initialized, it is 0. Actually I lied, In the Ethereum blockchain, no private variables are truly private. With a little snooping, any variables can be read. countResult is used to track the total number of Aye votes as voters made them. 

    uint public finalResult = 0;
    uint public totalVoter = 0;
    uint public totalVote = 0;

Lines 23 to 25: finalResult will store the final number of aye votes. It remains 0 until voting closes. Value of countResult (which is private) gets moved to finalResult (which is public) when vote closes. totalVoter is used to track the total number of voters in the voters register and totalVote is used to track the total number of votes cast. I am tracking these numbers as they change when, say, a new vote is cast instead of tallying the votes at the end of the voting process because I am avoiding the need to traverse the mappings (because you can’t traverse mappings in Solidity) and I do not wish to use arrays due to the high chance of running into gas limitation issues.

    address public ballotOfficialAddress;      
    string public ballotOfficialName;
    string public proposal;

Lines 26 to 28: The chairman’s name, wallet address and proposal are kept as public variables for everyone to see. 

    enum State { Created, Voting, Ended }
    State public state;

Lines 33 to 34: The ballot contract goes through 3 states. When the chairman creates it, it is in the state of “Created“. Once he indicates that voting starts, the ballot contract turns tot he state of “Voting“. When counting starts, the goes into the state of “Ended“. 

Preparing the Ballot

    constructor(
        string memory _ballotOfficialName,
        string memory _proposal) public {
        ballotOfficialAddress = msg.sender;
        ballotOfficialName = _ballotOfficialName;
        proposal = _proposal;
        
        state = State.Created;
    }

Lines 37 to 45: The chairman initializes the contract with the constructor by providing his _ballotOfficialName and _proposal. The constructor reads his wallet address and update the ballotOfficialAddress. This is important because folks who might wonder if this is a legitimate ballot can compare what they know is the chairman’s wallet address to what is saved here. At this point, the state of the contract is “Created“.

    function addVoter(address _voterAddress, string memory _voterName)
        public
        inState(State.Created)
        onlyOfficial
    {
        voter memory v;
        v.voterName = _voterName;
        v.voted = false;
        voterRegister[_voterAddress] = v;
        totalVoter++;
        emit voterAdded(_voterAddress);
    }

Lines 69 to 80: Next, the chairman add voters to the voterRegister mapping. This involves entering the voter’s wallet address and his name into the mapping. Line 71 states that this function is only executable when the contract is in the state of “Created“, so that no one, not even the chairman is allowed to add new voters once voting has begun. Lines 72 says onlyOfficial, which means that only the Chairman himself is allowed to run this function. You wouldn’t want the voter to be able to add himself to the voterRegister!

    modifier inState(State _state) {
        require(state == _state);
        _;
    }

The modifier for inState is declared from lines 58 to 61. It checks to ensure that the contract is currently in the state provided as variable to the inState() modifier.

    modifier onlyOfficial() {
        require(msg.sender ==ballotOfficialAddress);
        _;
    }

The onlyOfficial() modifier, declared from lines 53 to 56 checks to ensure that the wallet address of the person calling this function is the same address as what is saved in ballotOfficialAddress. This ensures that only the Chairman himself is allowed to call the function. In the event that the chairman denies ever starting the ballot, this will serve as evidence that it was really him, unless someone stole his wallet and private key.

    function startVote()
        public
        inState(State.Created)
        onlyOfficial
    {
        state = State.Voting;     
        emit voteStarted();
    }

Lines 83 to 90 lets the chairman starts the voting process. Note that this function only runs if the state of the contract is “Created” so there’s no way the chairman can restart the voting process. It also checks that onlyOfficial can run this function, so no one can do it other than the chairman himself. Line 88 changes the contract state from “Created” to “Voting“. Now voting can start.

Voting 

    function doVote(bool _choice)
        public
        inState(State.Voting)
        returns (bool voted)
    {
        bool found = false;
        
        if (bytes(voterRegister[msg.sender].voterName).length != 0 
        && !voterRegister[msg.sender].voted){
            voterRegister[msg.sender].voted = true;
            vote memory v;
            v.voterAddress = msg.sender;
            v.choice = _choice;
            if (_choice){
                countResult++; //counting on the go
            }
            votes[totalVote] = v;
            totalVote++;
            found = true;
        }
        emit voteDone(msg.sender);
        return found;
    }

Lines 93 to 115: This function is called to let voters vote. Voters can only vote if the contract’s state is “Voting” as stated in line 95. The condition statement in lines 100 and 101 checks for 2 conditions: the voter can only vote if his name is found in the voter’s register and if he hasn’t voted. His vote is registered into the votes mapping and his voted status is changed to true so that he cannot vote again. If he makes an “aye” choice, I add 1 to countResult. In this way, vote counting is done on the fly as voters vote. By doing this, I avoid the need to traverse through all the votes at the end of the voting process to count them because doing so is inherently expensive to do so and with a large vote turn out, the contract is likely to run out of gas.

Vote Counting (there’s none!)

    function endVote()
        public
        inState(State.Voting)
        onlyOfficial
    {
        state = State.Ended;
        finalResult = countResult; //move result from private countResult to public finalResult
        emit voteEnded(finalResult);
    }

Lines 118 to 126: The chairmain runs endVote() to end the ballot. The required state to run this function is “Voting” and onlyOfficial ensures that only the chairman can run this function. The function changes the contract state to “Ended” so that no voters can continue voting. It then updates finalResult (a public variable that everyone can read) with the value of countResult (a private variable that I used to accumulate vote counts as voters were voting). The concludes the ballot process.

Limitations and Improvements

Ballot.sol has several deadly limitations. Here’s a discussion of how it can do better.

Imagine that this is a national election with 10 million voters. A traversal of the votes registers will definitely exceed the gas limit of executing smart contracts on Ethereum. 

Smart Contract runs a single line of code at a time. The waiting time for someone else’s vote to get registered before yours will be prohibitively slow.

A single ballet contract is a single point of failure. If this breaks in the middle of the voting process, then there goes the entire election.

And I am sure we are going to encounter many more problems during load testing.

As an improvement, I am proposing that a future implementation will consist of a master ballot contract that spawns multiple children, each containing the same proposal, but registers up to say, n (where n could be the total number of people living in a certain area) voters.

Registered voters receive their personalized child ballot contract to vote with. This assignment can be done based on geography, time registered, or just arbitrarily.

When vote closes, each child ballot contract will track its own votes and report its result to the master ballot contract. The master ballot contract then consolidates and reports the final result.

This method mimics the way nationwide elections are conducted, where constituencies count their own votes and final vote counts are tallied at a national level.

Sounds like a fun project, but I will leave it for another day!    

Photo by Michael D Beckwith on Unsplash