Voting dApp series - A gentle introduction to Web3

Voting dApp series - A gentle introduction to Web3

I've been an active participant and benefactor of web2 over the last 10 years. Worked for most of the major web2 companies including the likes of Microsoft, Akamai, Oracle & Google.

Nothing has gotten me more excited than web3, which I've deliberated taken a plunge into in the last few weeks. As a beginner, I'm literally mind-blown by the number of community resources available to get things started on Web3. The sheer number of blogs, videos, tutorials & dApps are both intimidating and exciting at the same time.

So I decided to get my hands dirty with web3 and decided to write a small voting app (definitely inspired by some of the official examples available on the solidity documentation).

So what are going to build? A very, very, very basic voting dApp!

Now, what's a dApp? Read this gentle introduction to dApp here

Now, why Voting? From my research, I realised that the web3 differs from web2 at a fundamental level. Web3 is all about giving the power back to the users. It's changing the way technology is seen, operated, bought and sold at a foundational level.

Here are some examples of where Web3 is being transormative:

Infrastructure - Web3 shifts the cost of running the system to the same actors that benefit from it E.g. Decentralised Storage, Peer-to-Peer CDN, Decentralised Cloud etc.

Platform - Joining a social community/platform does not only make you a community member. It also makes you a co-owner of the platform. As a result, the platform is a sum of the contributors

Now, from my research, I've realised that Voting is one of the easiest ways to explain the principles of web3. Voting is the fundamental aspect of democracy. Ideally, it makes everyone participate and benefit from the platform at the same time. This is exactly why I believe "A voting app" is the "Hello World" of Web3.

Jumping right into the core constructs of our basic voting app. In this first release of our web3 voting app, we are going to be implementing a basic version with the following requirements:

Screenshot 2021-11-30 at 11.49.47 AM.png

Note: There's no central authority in this voting process. So the civilians cast their vote on the blockchain and the results are computed and stored in the same chain.

Here's what we need to implement this :

  • Solidity - A high-level language for implementing a smart contract
  • Hardhat - An Ethereum development environment for deploying our contract
  • Simulated Ethereum network (Hardhat handles this part).

For the impatient, you can find the code here First let's set up your project

Go to the folder where you want to run this project and do the following -

npm init -y 
npm install --save-dev hardhat
npx hardhat     //Creates a Basic project. You should see the project folder structure now. 
npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers     // installing dependencies
npx hardhat accounts       // Shows simulated ethereum addresses
npx hardhat compile  // Make sure it works fine
npx hardhat test // Should show sample test output

What should our smart contract execute?

  1. Each sender can vote only once
  2. The voter should be able to select the candidate of choice (proposal)
  3. Should be able to query the blockchain and find out who won

Under the contracts folder, create Ballot.sol

pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";

/// @author @courchrishi
/// @title Voting dApp - Part-1

contract Ballot {
    // Declares a complex type (Struct) for representing a single voter

    struct Voter {
        bool voted;     // if true, the person has already voted
        uint vote;      // index of the voted proposal
        uint weight;    // weight will be 1 since this is a non-delegated scenario
    }

    // Declares a complex type (Struct) for a single proposal
    struct Proposal {
        string name;      // short name (up to 32 bytes)
        uint voteCount;         // number of accummulated votes
    }

    // Declares a state variable (or) mapping that stores a 'Voter' struct for each address
    mapping(address => Voter) public voters;    

    // A dynamically-sized array of 'Proposal' structs
    Proposal[] public proposals;

    // A function to register the vote
    function vote(uint proposal) external {
       /// Initial checks for the sender before getting qualified as a voter
        Voter storage sender = voters[msg.sender]; // 1. Creating a Voter Struct and mapping to the sender's address
        require(!sender.voted, "Already voted");  // 2. Should not have already voted
        sender.voted = true; // Setting the voted flag to true
        sender.vote = proposal; // Setting the proposed vote to the vote property
        proposals[proposal].voteCount += 1;  // Updating the canditate's votecount

    }

    // /// Creates a new ballot to choose one of 'proposalNames'. Initiated at the time of contract deployment

    constructor(string[] memory proposalCandidates) {
        // 'Proposal({...}) creates a temporary proposal object 
        // proposals.push(...) appends it to the end of the proposals array

        for (uint i = 0; i < proposalCandidates.length; i++) {
            // `Proposal({...})` creates a temporary
            // Proposal object and `proposals.push(...)`
            // appends it to the end of `proposals`.
            proposals.push(Proposal({ name: proposalCandidates[i], voteCount:0}));
        }

    }

    function winningProposal() public view returns (uint winningProposal_) {\
        uint winningCount = 0; 
        for(uint i=0; i < proposals.length; i++) {
            if(proposals[i].voteCount > winningCount) {
                winningCount = proposals[i].voteCount;
                winningProposal_ = i;
                }
         }
    }

    function getElectionResult() external view returns(string memory winnerName_){
        winnerName_ = proposals[winningProposal()].name;
    }

}

Now, let us simulate a voting exercise where civilians cast their vote by registering their proposals. We will write logic to create the following tests -

  1. Upload the list of candidates to our "Ballot" smart contract
  2. Let 4 civilians cast their vote by registering their candidate proposal
  3. Query the blockchain to get the winner of the election

For this, we need to create a "run.js" file under the "Scripts" folder

const web3 = require("web3");

const main = async (names) => {
  const [owner, civilian1, civilian2, civilian3] =
  await hre.ethers.getSigners();
  const ballotFactory = await hre.ethers.getContractFactory("Ballot");
  console.log(typeof names);
  const ballotContract = await ballotFactory.deploy(names);

  await ballotContract.deployed();
  console.log("Contract Deployed to:", ballotContract.address);
  console.log("Contract deployed by:", owner.address);

  await ballotContract.vote(1);
  await ballotContract.connect(civilian1).vote(1);
  await ballotContract.connect(civilian2).vote(1);
  await ballotContract.connect(civilian3).vote(2);

  const winner = await ballotContract.getElectionResult();
  console.log(winner);
};

const runMain = async (names) => {
  try {
    await main(names);
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain(["candidate-1", "candidate-2", "candiate-3"]);

You should see the following output with the 'declared' winner coming from the blockchain -

Screenshot 2021-11-30 at 12.13.04 PM.png

That's it! You have a basic voting app working. As you must have noticed, this is a basic app. We still haven't considered many other conditions such as

  1. What if two candidates get equal vote?
  2. What if a voter wants to delegate their vote to someone else?
  3. How about having a chairperson who can give the right to vote each address individually?
  4. How about incentivising the voters to participate in the election by rewarding them with some ether?

In the next part, we will try to enhance our voting dApp with a few of the aforementioned features. Cheers!