Project Objectives – by building this project you will:
  • Combine a file-level struct, an enum, a mapping and a dynamic array in one contract.
  • Protect actions with modifiers (admin-only and one-vote-per-address).
  • Compute and publish an election result on-chain.
Synopsis

Online voting needs results that nobody can quietly alter. A blockchain is a good fit: every vote is a transaction recorded permanently, and anyone can check that the count follows the published rules.

In this project the deploying account becomes the BlockAdmin (election administrator). The admin cannot change votes; the admin’s role is to view the total number of votes cast and to calculate and publish the winner.

Every other account acts as a voter. A voter casts a vote by calling the function of the chosen candidate (or NOTA). The contract records the voter’s address and rejects a second vote from the same address, so no one can vote twice.

Contract Architecture
PartDetails
CandidateMaster.solFile-level struct Candidate { string candidateName; string candidateParty; uint voteCount; }
VotePanelContract.solContract VotePanel; imports contracts/CandidateMaster.sol
enumcandidateCode { AJIT, ASHISH, MAYANK, KUNAL, NOTA } – readable keys 0–4
mappingmapping(uint => Candidate) candidateList – candidate code → candidate record
dynamic arrayaddress[] votersList – addresses that have already voted
stateblockAdmin, totalCandidate (= uint(type(candidateCode).max) + 1 = 5), Candidate public winner
constructorSets blockAdmin = msg.sender and loads the five candidates (sample names) with 0 votes.
modifierscheckBlockAdmin() – only the admin; checkVoterCasting() – loops over votersList and rejects an address that already voted.
voting functionsOne function per candidate (e.g. Ajit_Singh__Red_Party(), NOTA()): records the voter and increases that candidate’s voteCount.
admin functionsvoteCountAndResult() – finds the candidate with the most votes and stores it in winner; totalVotesCasted(); viewBlockAdmin().
How It Works
1. DeployAdmin deploys; candidates are initialised
2. VoteEach voter calls one candidate function
3. CheckModifier blocks repeat voters
4. ResultAdmin runs voteCountAndResult(); winner is public

The result loop uses if (maxCount < candidate.voteCount), so in a tie the candidate with the lower code (declared earlier) remains the winner.

Key Requirements (Concepts Used)
  • Struct – Candidate record (Chapter 13) declared at file level and imported (Chapter 2).
  • Enum – candidate codes and type(candidateCode).max (Chapter 11).
  • Mapping – code → Candidate (Chapter 12).
  • Dynamic array – list of voters (Chapter 10).
  • Functions, view functions and public getters (Chapter 15).
  • Modifiers – admin and duplicate-vote checks (Chapter 22).
  • Constructor and msg.sender (Chapters 14 and 17).
How to Run in Remix
  1. In Remix, create contracts/CandidateMaster.sol and paste the CandidateMaster.sol resource file. The import path in the main file expects exactly this name and folder.
  2. Create contracts/VotePanelContract.sol and paste the VotePanelContract.sol resource file.
  3. Compile with a 0.8.x compiler and deploy VotePanel from the first account – it becomes BlockAdmin.
  4. Switch to other accounts and click one candidate function per account. Try voting twice from one account to see the “already casted” error.
  5. Switch back to the admin account, call voteCountAndResult(), then click winner and totalVotesCasted.
Limitations and Extension Exercises
Observation in the teaching versionImprovement to try
The duplicate check loops over every voter, so gas grows with each vote.Use mapping(address => bool) hasVoted for a constant-cost check.
One function per candidate; adding a candidate needs new code.Write one vote(candidateCode c) function.
Any address can vote; there is no voter registration.Let the admin register eligible voters (whitelist) before voting starts.
No voting period; the result can be computed at any time.Add start/end times or an enum state (Registration → Voting → Closed).
Ties are resolved silently in favour of the earlier candidate.Detect ties and report them.
Votes are public on-chain (no secret ballot).Study commit–reveal voting for privacy.
The function name Mayank_Jain__Green_Party does not match the stored party (“Blue Party”).Keep function names and data consistent – or use the single vote() function.
Exercise solution (one possible approach)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract VotePanelV2 {
    enum Code { AJIT, ASHISH, MAYANK, KUNAL, NOTA }

    address public immutable admin = msg.sender;
    bool public votingOpen = true;
    mapping(address => bool) public hasVoted;     // O(1) duplicate check
    mapping(Code => uint) public votes;
    uint public totalVotes;

    modifier onlyAdmin() { require(msg.sender == admin, "Not admin"); _; }

    function vote(Code c) external {
        require(votingOpen, "Voting closed");
        require(!hasVoted[msg.sender], "Already voted");
        hasVoted[msg.sender] = true;
        votes[c] += 1;
        totalVotes += 1;
    }

    function closeVoting() external onlyAdmin {
        votingOpen = false;
    }

    function leader() external view returns (Code best, uint count) {
        for (uint i = 0; i <= uint(type(Code).max); i++) {
            if (votes[Code(i)] > count) {
                count = votes[Code(i)];
                best = Code(i);
            }
        }
    }
}
Key Takeaways
  • Structs, enums, mappings and arrays together model candidates and voters.
  • Modifiers enforce the rules: only the admin publishes results; each address votes once.
  • Mappings are better than array loops for “has this address already acted?” checks.
Video : Project 1 : Smart Contract for Election System
Resource Files
Question / Answer (15 questions)

Answer : BlockAdmin is the account that deploys the contract. It can view the total votes cast and calculate and publish the winner, but it cannot change anyone’s vote.

Answer : In the constructor: blockAdmin = msg.sender;, so the deploying account becomes the admin.

Answer : struct Candidate { string candidateName; string candidateParty; uint voteCount; }, defined at file level in CandidateMaster.sol and imported into VotePanelContract.sol.

Answer : The enum candidateCode (AJIT, ASHISH, MAYANK, KUNAL, NOTA) gives readable names to candidates; converting a member with uint(...) provides the key for the candidate mapping.

Answer : uint(type(candidateCode).max) + 1. The last enum member NOTA has value 4, so the total is 5.

Answer : The checkVoterCasting() modifier loops over votersList; if msg.sender is already in the list, require fails with “You have already casted Your Voter”.

Answer : It adds msg.sender to votersList, loads the candidate from the mapping, increments voteCount and stores the updated candidate back.

Answer : It loops over all candidate codes, keeps track of the highest voteCount seen so far and stores that candidate in the public winner variable.

Answer : Because the comparison is strictly less-than, the first candidate (lower code) that reached the highest count remains the winner; the tie is not reported.

Answer : voteCountAndResult(), totalVotesCasted() and viewBlockAdmin(), all protected by checkBlockAdmin().

Answer : It loops over every address in votersList, so the gas cost of voting grows with the number of voters. A mapping(address => bool) would make the check constant-cost.

Answer : Because VotePanelContract.sol imports "contracts/CandidateMaster.sol"; the compiler can only resolve the import if a file exists at that path.

Answer : No. Every vote is a public transaction showing which candidate function each address called. Secret ballots need techniques such as commit–reveal or cryptographic voting schemes.

Answer : Use a single vote(candidateCode) function with a mapping(address => bool) for duplicate checks, and add voter registration plus a voting period (start/end or state enum) so results can only be declared after voting closes.

Answer : Votes recorded as transactions are permanent and transparent, the counting rules are public code that runs the same way on every node, and no single party can alter recorded votes.
MCQ Test : Check Your Understanding (5 questions)

Select an option and press Check Answer, or answer all questions and press Submit Test to see your score. Each answer comes with an explanation.

Q1. In the Election contract, who becomes the BlockAdmin?

Show Answer

Correct Answer : B. The account that deploys the contract
The constructor stores msg.sender as blockAdmin.

Q2. uint(type(candidateCode).max) + 1 evaluates to:

Show Answer

Correct Answer : C. 5
NOTA is 4, plus 1 = 5 candidates.

Q3. Which modifier prevents double voting?

Show Answer

Correct Answer : A. checkVoterCasting()
It checks votersList for msg.sender.

Q4. Where are the addresses of voters stored?

Show Answer

Correct Answer : D. In the dynamic array votersList
address[] votersList records voters.

Q5. Which data structure gives a constant-cost “has voted” check?

Show Answer

Correct Answer : A. mapping(address => bool)
A mapping lookup does not need a loop.

Bibliography/References

The notes, questions and MCQs on this page are original explanations written for this course. The works listed below were consulted and are cited for attribution and further reading. Product names, logos and trademarks belong to their respective owners. Short quotations, where used, are marked and attributed.

  1. Solidity Team. (n.d.). Types (value types, reference types, mappings, operators, conversions). Solidity Documentation. https://docs.soliditylang.org/en/latest/types.html
  2. Solidity Team. (n.d.). Contracts (creating contracts, visibility and getters, function modifiers, constant and immutable, functions, events, custom errors, inheritance, abstract contracts, interfaces, libraries). Solidity Documentation. https://docs.soliditylang.org/en/latest/contracts.html
  3. Solidity Team. (n.d.). Security Considerations. Solidity Documentation. https://docs.soliditylang.org/en/latest/security-considerations.html
  4. Remix Project. (n.d.). Remix IDE Documentation. https://remix-ide.readthedocs.io/en/latest/
  5. ethereum.org. (n.d.). Smart contract security. Ethereum Developer Documentation. https://ethereum.org/en/developers/docs/smart-contracts/security/
  6. Modi, R. (2018). Solidity Programming Essentials: A beginner’s guide to build smart contracts for Ethereum and blockchain. Packt Publishing. ISBN 978-1-78883-138-3.