Overview of the Voting System

The decentralized voting system we will build will include the following features:

  • Voter Registration: Only registered voters can participate in the election.
  • Weighted Votes: Votes will be weighted based on the number of tokens held by a voter.
  • Voting Process: Voters can cast their votes for multiple candidates.
  • Results Calculation: The system will tally votes and determine the winning candidate.

Smart Contract Structure

We'll define a simple structure for our voting system, including the following components:

  1. Voter Struct: To store information about each voter.
  2. Candidate Struct: To represent candidates in the election.
  3. Voting Logic: Functions for registering voters, casting votes, and calculating results.

Code Example

Here is the complete code for our decentralized voting system:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract WeightedVoting {
    struct Voter {
        bool isRegistered;
        uint256 weight; // Weight based on tokens
        uint256 vote; // Candidate voted for
    }

    struct Candidate {
        string name;
        uint256 voteCount;
    }

    address public chairperson;
    mapping(address => Voter) public voters;
    Candidate[] public candidates;

    constructor(string[] memory candidateNames) {
        chairperson = msg.sender;
        for (uint256 i = 0; i < candidateNames.length; i++) {
            candidates.push(Candidate({ name: candidateNames[i], voteCount: 0 }));
        }
    }

    modifier onlyChairperson() {
        require(msg.sender == chairperson, "Only chairperson can call this function");
        _;
    }

    function registerVoter(address voter, uint256 weight) public onlyChairperson {
        require(!voters[voter].isRegistered, "Voter is already registered");
        voters[voter] = Voter({ isRegistered: true, weight: weight, vote: 0 });
    }

    function castVote(uint256 candidateIndex) public {
        Voter storage sender = voters[msg.sender];
        require(sender.isRegistered, "You are not registered to vote");
        require(sender.vote == 0, "You have already voted");

        sender.vote = candidateIndex + 1; // Store vote (1-indexed)
        candidates[candidateIndex].voteCount += sender.weight; // Weight applied
    }

    function getResults() public view returns (string memory winnerName, uint256 winnerVoteCount) {
        uint256 winningVoteCount = 0;
        for (uint256 i = 0; i < candidates.length; i++) {
            if (candidates[i].voteCount > winningVoteCount) {
                winningVoteCount = candidates[i].voteCount;
                winnerName = candidates[i].name;
            }
        }
        winnerVoteCount = winningVoteCount;
    }
}

Explanation of the Code

  1. Struct Definitions:
  • Voter: Contains information about whether the voter is registered, their voting weight, and the candidate they voted for.
  • Candidate: Represents a candidate with a name and vote count.
  1. Constructor:
  • Initializes the contract with a list of candidate names and sets the chairperson.
  1. Modifiers:
  • onlyChairperson: Ensures that only the chairperson can call certain functions, such as registering voters.
  1. Functions:
  • registerVoter: Allows the chairperson to register a voter with a specified weight.
  • castVote: Allows registered voters to cast their votes for a candidate. It checks that the voter is registered and has not already voted. The vote is weighted based on the voter's registered weight.
  • getResults: Returns the name and vote count of the winning candidate.

Best Practices

  • Access Control: Use modifiers to restrict access to sensitive functions.
  • Data Validation: Ensure that inputs are validated to prevent unexpected behavior.
  • Gas Optimization: Minimize state changes and external calls to reduce gas costs.

Testing the Contract

To test the contract, you can deploy it using Remix or any Ethereum development framework such as Truffle or Hardhat. Here are some sample interactions you can perform:

  1. Deploy the Contract:
  2. Deploy the WeightedVoting contract with an array of candidate names.

  1. Register Voters:
  2. Use the registerVoter function to register different addresses with varying weights.

  1. Cast Votes:
  2. Call the castVote function from registered addresses to vote for candidates.

  1. Get Results:
  2. After all votes are cast, call getResults to determine the winner.

Conclusion

In this tutorial, we have built a decentralized voting system in Solidity that allows for weighted votes. This system can be expanded further with additional features such as voting deadlines, candidate registration by voters, and more complex voting mechanisms. By following best practices in smart contract development, you can ensure that your voting system is secure and efficient.

Learn more with useful resources