Developing the Solidity Smart Contract for Our Voting DApp

chainyblockchainyblock
6 min read

Welcome back to our series on building a decentralized voting application (DApp) using the Ethereum network! In Article 1 , we explored the logic, entities, and interactions in our voting system. Now it’s time to dive into the development of the smart contract that powers this DApp.

In this article, we’ll break down the Solidity code into meaningful sections, focusing on each entity (Election, Candidate, Voter) and their associated functions. By the end, you’ll have a clear understanding of how the backend of our voting DApp works. Let’s get started!

Setting Up the Foundation

Before diving into the entities, let’s set up the basic structure of the smart contract:

  • Contract Owner : The owner is the person who deploys the smart contract and has special permissions, such as creating elections and ending them.

  • Modifiers : These are reusable pieces of code that enforce rules, like ensuring only the owner can perform certain actions or that an election exists before interacting with it.

address public owner;

modifier onlyOwner() {
    require(msg.sender == owner, "Only the owner can call this function");
    _;
}

modifier electionExists(uint256 _electionId) {
    require(_electionId > 0 && _electionId <= electionCount, "Election does not exist");
    _;
}

The onlyOwner modifier ensures that only the contract deployer can call specific functions, while electionExists ensures that an election ID is valid before proceeding.

Elections: The Backbone of the System

An election is the core entity in our voting system. It defines when voting starts and ends, tracks candidates and voters, and determines whether it’s active.

Election Structure

struct Election {
    uint256 id;
    string name;
    string description;
    uint256 startTime;
    uint256 endTime;
    bool isActive;
    uint256 candidateCount;
    uint256 voterCount;
}

Each election has:

  • A unique ID.

  • A name and description.

  • Start and end times.

  • A status (isActive) to indicate whether it’s currently running.

  • Counters for candidates and voters.

Creating an Election

The createElection function allows the owner to create a new election. It enforces rules like ensuring the start time is in the future and the end time is after the start time.

function createElection(
    string memory _name,
    string memory _description,
    uint256 _startTime,
    uint256 _endTime
) public onlyOwner {
    require(_startTime > block.timestamp, "Start time must be in the future");
    require(_endTime > _startTime, "End time must be after start time");

    electionCount++;
    elections[electionCount] = Election({
        id: electionCount,
        name: _name,
        description: _description,
        startTime: _startTime,
        endTime: _endTime,
        isActive: true,
        candidateCount: 0,
        voterCount: 0
    });

    emit ElectionCreated(electionCount, _name, _startTime, _endTime);
}

This function creates a new election, initializes its details, and emits an event to log the creation on the blockchain.

Candidates: Representing Choices in the Election

Candidates are individuals running for office in a specific election. Each candidate is tied to an election and tracks their vote count.

Candidate Structure

struct Candidate {
    uint256 id;
    string name;
    string partyAffiliation;
    string personalInfo;
    uint256 voteCount;
    uint256 electionId;
}

Each candidate has:

  • A unique ID.

  • A name, party affiliation, and personal information.

  • A vote count to track how many votes they’ve received.

  • An association with the election they’re running in.

Registering a Candidate

The registerCandidate function allows candidates to be registered for a specific election. Candidates can only be registered before the election starts.

function registerCandidate(
    uint256 _electionId,
    string memory _name,
    string memory _partyAffiliation,
    string memory _personalInfo
) public electionExists(_electionId) {
    require(elections[_electionId].isActive, "Election is not active");
    require(block.timestamp < elections[_electionId].startTime, "Registration period has ended");

    candidateCount++;
    elections[_electionId].candidateCount++;

    candidates[candidateCount] = Candidate({
        id: candidateCount,
        name: _name,
        partyAffiliation: _partyAffiliation,
        personalInfo: _personalInfo,
        voteCount: 0,
        electionId: _electionId
    });

    emit CandidateRegistered(candidateCount, _name, _electionId);
}

This function ensures that candidates are registered within the allowed timeframe and links them to the correct election.

Voters: The Participants in the Election

Voters are the people who participate in the election by casting their votes. Each voter is identified by their Ethereum address and must meet eligibility requirements.

Voter Structure

struct Voter {
    address voterAddress;
    string name;
    uint256 age;
    string gender;
    bool isRegistered;
    mapping(uint256 => bool) hasVoted; // Election ID => voted or not
}

Each voter has:

  • A unique Ethereum address.

  • Personal details like name, age, and gender.

  • A registration status (isRegistered) to ensure only eligible voters can participate.

  • A mapping to track whether they’ve voted in specific elections.

Registering a Voter

The registerVoter function allows users to register themselves as voters. They must be at least 18 years old.

function registerVoter(string memory _name, uint256 _age, string memory _gender) public {
    require(!voters[msg.sender].isRegistered, "Voter already registered");
    require(_age >= 18, "Voter must be at least 18 years old");

    Voter storage voter = voters[msg.sender];
    voter.voterAddress = msg.sender;
    voter.name = _name;
    voter.age = _age;
    voter.gender = _gender;
    voter.isRegistered = true;

    emit VoterRegistered(msg.sender, _name);
}

This function ensures that voters meet the age requirement and prevents duplicate registrations.

Casting a Vote

The castVote function allows registered voters to cast their votes during the active election period. Each voter can only vote once per election.

function castVote(uint256 _electionId, uint256 _candidateId) public 
    electionExists(_electionId) 
    candidateExists(_candidateId) 
    electionActive(_electionId) 
{
    require(voters[msg.sender].isRegistered, "Voter not registered");
    require(!voters[msg.sender].hasVoted[_electionId], "Already voted in this election");
    require(candidates[_candidateId].electionId == _electionId, "Candidate not in this election");

    if (!voters[msg.sender].hasVoted[_electionId]) {
        elections[_electionId].voterCount++;
    }

    voters[msg.sender].hasVoted[_electionId] = true;
    candidates[_candidateId].voteCount++;

    emit VoteCasted(msg.sender, _electionId, _candidateId);
}

This function ensures that only eligible voters can vote and prevents double voting.

Ending an Election and Determining the Winner

Once the election ends, the owner can call the endElection function to calculate the winner based on the highest number of votes.

Ending an Election

function endElection(uint256 _electionId) public onlyOwner electionExists(_electionId) {
    require(elections[_electionId].isActive, "Election already ended");
    require(block.timestamp > elections[_electionId].endTime, "Election period not over yet");

    elections[_electionId].isActive = false;

    uint256 winnerCandidateId = getWinner(_electionId);
    if (winnerCandidateId > 0) {
        emit ElectionEnded(
            _electionId, 
            winnerCandidateId, 
            candidates[winnerCandidateId].name, 
            candidates[winnerCandidateId].voteCount
        );
    }
}

Calculating the Winner

The getWinner function determines the candidate with the highest number of votes.

function getWinner(uint256 _electionId) public view electionExists(_electionId) returns (uint256 winnerId) {
    require(block.timestamp > elections[_electionId].endTime, "Election has not ended yet");

    uint256 maxVotes = 0;
    winnerId = 0;

    for (uint256 i = 1; i <= elections[_electionId].candidateCount; i++) {
        uint256 candidateId = electionToCandidateIds[_electionId][i];
        if (candidates[candidateId].voteCount > maxVotes) {
            maxVotes = candidates[candidateId].voteCount;
            winnerId = candidateId;
        }
    }

    return winnerId;
}

Wrapping Up

Great job! You now understand how the smart contract handles elections, candidates, and voters. In the next article, we’ll test this smart contract using Hardhat and deploy it to the Ethereum network.

Your Task : Think about how you might extend this voting system. Could you add features like multi-election support for the same voter or real-time vote tracking? Share your ideas in the comments below!

Stay tuned for Article 3, where we’ll test and deploy the smart contract. See you there! 🚀

0
Subscribe to my newsletter

Read articles from chainyblock directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

chainyblock
chainyblock

👋 Hi, We are ChainyBlock, a passionate advocate for blockchain technology and its transformative potential. With a background in software engineering and cybersecurity, We've spent a lot of time exploring how decentralized systems can reshape industries, foster trust, and create a more inclusive future. 🎯 What Drives Me? I believe that understanding complex technologies like blockchain shouldn’t be reserved for experts—it should be accessible to everyone. That’s why I’m here: to break down the fundamentals of Web3, cryptocurrencies, smart contracts, and decentralized applications into simple, actionable insights. Whether you’re a beginner or a seasoned learner, my goal is to help you navigate this rapidly evolving space with confidence. 💭 Dreams for the Future: I dream of a world where blockchain technology enables secure, transparent, and efficient systems for everyone—regardless of location or background. Through education and collaboration, I hope to inspire others to embrace the possibilities of Web3 and contribute to this global movement. 🌟 Let’s Connect: Feel free to reach out if you’d like to discuss blockchain innovations, collaborate on projects, or share ideas. Together, we can build a smarter, decentralized future. 🌐💡