§ 3.3Module 3

Case Study — Voting Contract App

3.3 — Case Study: Voting Contract App

Recall first. A voting rule needs three things: a set of candidates, a count for each candidate, and a way to reject a second vote from the same address. Which Solidity features from 3.2 express those three requirements?

Source boundary: syllabus bridge, not a book program

The book mentions that Ethereum contracts can be used for voting from a valid address in the introduction to Chapter 14. It also discusses DAO proposals, stakeholder consensus, contractors, and voting in Chapter 5.4.3. However, the supplied book extract does not provide the complete voting-contract application below.12

This note is therefore an explicit syllabus bridge/book gap. The contract is a small educational synthesis of book-covered concepts—struct, mapping, dynamic array, modifier, event, require, functions, msg.sender, and view—not a claim that the book supplied this exact application.

Requirements

Build the smallest useful on-chain voting exercise:

  1. Store candidate names and vote counts.
  2. Let an address vote for one valid candidate.
  3. Reject an invalid candidate index.
  4. Reject a second vote from the same address.
  5. Emit a log when a vote succeeds.
  6. Expose read functions so a client can inspect the result.

Deliberate limits

This is not a production election system. It has no identity verification, Sybil resistance, secret ballots, proposal lifecycle, tie-breaking policy, vote-weight snapshot, administrator, or front-end. One address is treated as one voter, but one person may control multiple addresses. Those omissions are deliberate so that the syllabus concepts remain visible.

Educational contract — Solidity 0.5.1

pragma solidity ^0.5.1;

contract EducationalVoting {
    struct Candidate {
        string name;
        uint voteCount;
    }

    Candidate[] public candidates;
    mapping(address => bool) public hasVoted;

    event VoteCast(address indexed voter, uint candidateIndex);

    constructor(string memory firstName, string memory secondName) public {
        candidates.push(Candidate(firstName, 0));
        candidates.push(Candidate(secondName, 0));
    }

    modifier candidateExists(uint candidateIndex) {
        require(candidateIndex < candidates.length, "invalid candidate");
        _;
    }

    function vote(uint candidateIndex)
        public
        candidateExists(candidateIndex)
    {
        require(!hasVoted[msg.sender], "already voted");

        hasVoted[msg.sender] = true;
        candidates[candidateIndex].voteCount += 1;
        emit VoteCast(msg.sender, candidateIndex);
    }

    function candidateCount() public view returns (uint) {
        return candidates.length;
    }

    function leadingCandidate()
        public
        view
        returns (uint index, uint votes)
    {
        require(candidates.length > 0, "no candidates");
        index = 0;
        votes = candidates[0].voteCount;

        for (uint i = 1; i < candidates.length; i++) {
            if (candidates[i].voteCount > votes) {
                index = i;
                votes = candidates[i].voteCount;
            }
        }
    }
}

Compiler note. Select a compatible 0.5.x compiler in Remix. Modern Solidity may require syntax changes; do not silently compile this old example with an incompatible compiler. The version choice follows the book’s pragma solidity ^0.5.1 discussion.3

Read the contract as a data model

RequirementSolidity elementWhy
Candidate recordstruct Candidatename and count stay together
Candidate collectionCandidate[] candidatespreserves candidate indexes/order
One-vote flagmapping(address => bool) hasVoteddirect lookup by caller address
Valid index rulecandidateExists modifierreusable precondition
Failurerequirerejected calls do not apply the state change
Audit/UI signalVoteCast eventexternal clients can listen to the log
Caller identitymsg.senderidentifies the address submitting the vote
Result queryview functionsread without a state-changing action

Constructor

Deployment supplies two names. The constructor pushes two Candidate records into the dynamic storage array. It runs once, so it is a suitable place for initial setup.3

vote

The modifier first rejects an out-of-range candidate index. The function then checks the caller’s mapping entry, marks the caller as having voted, increments the selected record, and emits an event. A failed require stops execution; the attempted state change is not committed.3

The order is important: validate before changing state. In a larger contract, checks-effects-interactions is also a useful safety discipline, but this small example performs no external call.

leadingCandidate

The function scans the array and returns the first candidate with the greatest count. This is an O(n) educational scan. It leaves ties unresolved by policy: the first candidate wins the returned index. A real application must state its tie rule explicitly.

Worked example — trace three accounts

Deploy with "Alice" and "Bob":

candidates[0] = Alice, 0
candidates[1] = Bob,   0

Suppose three different accounts call:

CallMapping effectCandidate counts
Account A: vote(1)hasVoted[A] = trueAlice 0, Bob 1
Account B: vote(0)hasVoted[B] = trueAlice 1, Bob 1
Account A: vote(0)rejected by requireAlice 1, Bob 1

The third call fails because the address, not the candidate, identifies the previous voter. The failed call does not increment Alice and does not emit VoteCast.

If Account C calls vote(4), the modifier fails before the vote body runs because the array contains only indexes 0 and 1.

What the app demonstrates—and what it does not

Demonstrates

Does not demonstrate

Exercise

  1. Why are both Candidate[] and mapping(address => bool) needed?
  2. What happens if the same address calls vote(0) and then vote(1)?
  3. What is the result of a tie in this implementation?
  4. Which operation creates a transaction: candidateCount() or vote(0)? Explain.
  5. Add a requirement in words that would prevent voting after a deadline. What additional state would be needed?
  6. Name one reason this contract cannot provide a secure public election.
Revealed answers
  1. The array enumerates candidates and stores their records; the mapping performs a direct per-address “already voted?” check.
  2. The first call succeeds and sets the mapping entry. The second call reverts, so the second candidate’s count is unchanged.
  3. leadingCandidate returns the first candidate among tied candidates because it updates only when a strictly greater count is found. This is a design choice, not a universal voting rule.
  4. vote(0) changes storage and must be submitted as a transaction with gas. candidateCount() is view, so it can normally be read without a state-changing transaction.
  5. Add a state variable such as uint votingEndsAt and require now < votingEndsAt in a version-compatible implementation. The deadline itself must be set and interpreted carefully.
  6. Any of: no identity/Sybil resistance, no secret ballot, observable votes, no protection for compromised keys, or no governance/audit process around deployment.

Exam lens

If asked to design a voting contract, write the data model first:

Candidate { name, voteCount }
Candidate[] candidates
mapping(address => bool) hasVoted

Then explain the state transition:

valid candidate?
    → caller has not voted?
        → mark caller
        → increment candidate
        → emit event

Mention that the full app is a syllabus bridge: the book supplies the component concepts and indirectly mentions voting, but not this complete program.12

Likely marks: structure/pragma, constructor, array and struct, mapping, modifier and require, event, state-changing vote function, read function, and one limitation.

Common traps:

Rapid revision

Key takeaways

Sources

Footnotes

  1. Blockchain Technology (converted book extract), Ch. 14 introduction, Markdown lines 8375–8377: smart contracts can be used for voting from a valid address. 2

  2. Blockchain Technology (converted book extract), Ch. 5, §5.4.3, Markdown lines 4022–4039: DAO consensus, proposals, contractors, and voting. 2

  3. Blockchain Technology (converted book extract), Ch. 14, §§14.1–14.17, Markdown lines 8367–8811: Solidity contracts, constructors, arrays, mappings, modifiers, events, functions, and errors. 2 3