E1 - RCVotingSystem.test.js

Gas Report

Unit Tests and Coverage

RCVotingSystem.test.js

const { expect } = require("chai")
const { ethers } = require("hardhat")
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers")

describe("VotingContract", function () {
    let VotingContract,
        votingContract,
        owner,
        addr1,
        addr2,
        addr3,
        addr4,
        vote,
        rankedChoices,
        BallotContract,
        ballotContract,
        ballotContractAddress,
        addElection,
        election1id,
        election2id,
        oneDay

    beforeEach(async function () {
        ;[owner, addr1, addr2, addr3, addr4] = await ethers.getSigners()

        BallotContract = await ethers.getContractFactory("BallotContract")
        ballotContract = await BallotContract.deploy()
        ballotContractAddress = ballotContract.target
        VotingContract = await ethers.getContractFactory("VotingContract")
        votingContract = await VotingContract.deploy(ballotContractAddress)
        rankedChoices = [0, 1, 2]
        election1id = 1
        election2id = 2
        oneDay = 1
        candidate1 = "candidate 1"
        candidate2 = "candidate 2"
        candidate3 = "candidate 3"

        addElection = await ballotContract.addElection(
            [candidate1, candidate2, candidate3],
            oneDay,
        )
    })

    describe("Deployment", function () {
        it("Should set the right owner", async function () {
            expect(await votingContract.getOwner()).to.equal(owner.address)
        })
    })

    describe("vote", function () {
        it("Reverts if voter has already voted", async function () {
            vote = await votingContract
                .connect(addr1)
                .addVotes(rankedChoices, oneDay)
            await expect(
                votingContract.connect(addr1).addVotes(rankedChoices, 1),
            ).to.be.revertedWith("Voter has already voted")
        })

        it("Reverts if election does not exist", async function () {
            await expect(
                votingContract.connect(addr1).addVotes(rankedChoices, 0),
            ).to.be.revertedWith("Election does not exist")
            await expect(
                votingContract.connect(addr1).addVotes(rankedChoices, 2),
            ).to.be.revertedWith("Election does not exist")
            await expect(
                votingContract.connect(addr1).addVotes(rankedChoices, 100),
            ).to.be.revertedWith("Election does not exist")
        })

        it("Reverts if election is closed", async function () {
            await time.increase(2 * 24 * 60 * 60)

            await ballotContract.connect(owner).closeElection(election1id)

            await expect(
                votingContract
                    .connect(addr1)
                    .addVotes(rankedChoices, election1id),
            ).to.be.revertedWith("Election is closed")
        })

        it("Reverts if there is a different number of candidates", async function () {
            await expect(
                votingContract.connect(addr1).addVotes([1, 2], 1),
            ).to.be.revertedWith(
                "The amount of votes does not match the amount of candidates",
            )
            await expect(
                votingContract.connect(addr1).addVotes([1, 2, 3, 4], 1),
            ).to.be.revertedWith(
                "The amount of votes does not match the amount of candidates",
            )
        })

        it("Should allow a voter to vote", async function () {
            vote = await votingContract
                .connect(addr1)
                .addVotes(rankedChoices, oneDay)
            expect(
                await votingContract.getVoterChoices(addr1, election1id),
            ).to.deep.equal(rankedChoices)
        })

        it("Voter status updates correctly", async function () {
            expect(
                await votingContract.getVoterStatus(addr1, election1id),
            ).to.equal(false)

            vote = await votingContract
                .connect(addr1)
                .addVotes(rankedChoices, oneDay)
            expect(
                await votingContract.getVoterStatus(addr1, election1id),
            ).to.equal(true)
        })

        it("Should update the candidateVoteCount mapping correctly", async function () {
            vote = await votingContract
                .connect(addr1)
                .addVotes(rankedChoices, 1)
            expect(
                await ballotContract.getCandidateVoteCount(
                    election1id,
                    candidate1,
                ),
            ).to.deep.equal([1, 0, 0])
            expect(
                await ballotContract.getCandidateVoteCount(
                    election1id,
                    candidate2,
                ),
            ).to.deep.equal([0, 1, 0])
            expect(
                await ballotContract.getCandidateVoteCount(
                    election1id,
                    candidate3,
                ),
            ).to.deep.equal([0, 0, 1])
        })

        it("should count multiple votes properly", async function () {
            candidate4 = "candidate 4"

            await ballotContract.addElection(
                [candidate1, candidate2, candidate3, candidate4],
                oneDay,
            )

            await votingContract.connect(addr1).addVotes([0, 1, 2, 3], 2)
            await votingContract.connect(addr3).addVotes([2, 3, 0, 1], 2)
            await votingContract.connect(addr4).addVotes([1, 2, 3, 0], 2)
            await votingContract.connect(addr2).addVotes([1, 2, 3, 0], 2)

            expect(
                await ballotContract.getCandidateVoteCount(2, candidate1),
            ).to.deep.equal([1, 0, 1])
            expect(
                await ballotContract.getCandidateVoteCount(2, candidate2),
            ).to.deep.equal([2, 1, 0])
            expect(
                await ballotContract.getCandidateVoteCount(2, candidate3),
            ).to.deep.equal([1, 2, 1])
            expect(
                await ballotContract.getCandidateVoteCount(2, candidate4),
            ).to.deep.equal([0, 1, 2])
        })

        it("Should emit a VoteCast event", async function () {
            vote = await votingContract
                .connect(addr1)
                .addVotes(rankedChoices, oneDay)
            await expect(vote).to.emit(votingContract, "VoteCast")
        })
    })

    describe("getVoterChoices", function () {
        it("Should revert if account has not voted", async function () {
            await expect(
                votingContract.getVoterChoices(addr1, election1id),
            ).to.be.revertedWith("Voter has not voted")
        })
    })

    describe("GetElectionCandidates", function () {
        it("Returns election candidates to Voting Contract", async function () {
            expect(await votingContract.getElectionCandidates(1)).to.deep.equal(
                ["candidate 1", "candidate 2", "candidate 3"],
            )
        })
    })
    describe("GetElectionStatus", function () {
        it("Returns election status to Voting Contract", async function () {
            expect(await votingContract.getElectionStatus(1)).to.equal(true)
        })
    })
})

describe("BallotContract", function () {
    let BallotContract,
        ballotContract,
        owner,
        addr1,
        addr2,
        addr3,
        addr4,
        addr5,
        candidatesList,
        candidate1,
        candidate2,
        candidate3,
        electionTimeLimit,
        electionStartTime,
        electionEndTime

    beforeEach(async function () {
        BallotContract = await ethers.getContractFactory("BallotContract")
        ;[owner, addr1, addr2, addr3, addr4, addr5] = await ethers.getSigners()
        ballotContract = await BallotContract.deploy()

        candidate1 = "Candidate 1"
        candidate2 = "Candidate 2"
        candidate3 = "Candidate 3"
    })

    describe("Deployment", function () {
        it("Should set the right owner", async function () {
            expect(await ballotContract.getOwner()).to.equal(owner.address)
        })
    })

    describe("addElection", function () {
        beforeEach(async function () {
            electionTimeLimit = 1

            await ballotContract.addElection(
                [candidate1, candidate2, candidate3],
                electionTimeLimit,
            )

            candidatesList = await ballotContract.getElectionCandidates(1)
            electionStatus = await ballotContract.getElectionStatus(1)
            electionStartTime = await ballotContract.getElectionStartTime(1)
            electionEndTime = await ballotContract.getElectionEndTime(1)
        })

        it("onlyOwner can add an election", async function () {
            await expect(
                ballotContract
                    .connect(addr1)
                    .addElection(
                        ["Candidate 1", "Candidate 2", "Candidate 3"],
                        electionTimeLimit,
                    ),
            ).to.be.revertedWith("Only the owner can call this function")
        })
        it("Reverts is there is only one candidate", async function () {
            await expect(
                ballotContract.addElection(["only1Candidate"], 1),
            ).to.be.revertedWith("There must be more than one candidate")
        })

        it("Reverts if there are more than 5 candidates", async function () {
            await expect(
                ballotContract.addElection(
                    [
                        "Candidate1/6",
                        "Candidate2/6",
                        "Candidate3/6",
                        "Candidate4/6",
                        "Candidate5/6",
                        "Candidate6/6",
                    ],
                    electionTimeLimit,
                ),
            ).to.be.revertedWith(
                "An election can have a maximum of 5 candidates",
            )
        })

        it("Succesfully creates and maps an election", async function () {
            expect(await ballotContract.getElection(1)).to.deep.equal([
                candidatesList,
                electionStartTime,
                electionEndTime,
                electionStatus,
            ])
        })

        it("Should correctly set electionOpen bool to true", async function () {
            expect(await ballotContract.getElectionStatus(1)).to.equal(true)
        })

        it("Should incriment the Election count after each election", async function () {
            const electionCount = await ballotContract.getElectionCount()
            const counterIncriment = ethers.parseUnits("1", 0)
            await ballotContract.addElection(
                ["Candidate 1", "Candidate 2", "Candidate 3"],
                electionTimeLimit,
            )
            expect(await ballotContract.getElectionCount()).to.equal(
                electionCount + counterIncriment,
            )
        })

        it("Adds electionId to openElections array", async function () {
            expect(await ballotContract.getOpenElections()).to.deep.equal([1])
        })

        it("Should add candidates to the candidateVoteCount mapping", async function () {
            expect(
                await ballotContract.getCandidateVoteCount(1, candidate1),
            ).to.deep.equal([0, 0, 0])
        })

        it("Should emit an ElectionCreated event", async function () {
            await expect(
                ballotContract.addElection(
                    ["Candidate 1", "Candidate 2", "Candidate 3"],
                    electionTimeLimit,
                ),
            ).to.emit(ballotContract, "ElectionCreated")
        })
    })

    describe("closeElection", function () {
        beforeEach(async function () {
            electionTimeLimit = 1

            await ballotContract.addElection(
                [candidate1, candidate2, candidate3],
                electionTimeLimit,
            )

            await time.increase(electionTimeLimit * 24 * 60 * 60)
        })

        it("onlyOwner can close an election", async function () {
            await expect(
                ballotContract.connect(addr1).closeElection(1),
            ).to.be.revertedWith("Only the owner can call this function")
        })

        it("Reverts if election does not exist", async function () {
            await expect(ballotContract.closeElection(0)).to.be.revertedWith(
                "Election does not exist",
            )
            await expect(ballotContract.closeElection(100)).to.be.revertedWith(
                "Election does not exist",
            )
        })

        it("Reverts if election is already closed", async function () {
            const closedElection = await ballotContract.closeElection(1)
            closedElection.wait()
            await expect(ballotContract.closeElection(1)).to.be.revertedWith(
                "Election is already closed",
            )
        })

        it("Succesfully closes an election and sets status to closed", async function () {
            const closedElection = await ballotContract.closeElection(1)
            await closedElection.wait()
            expect(await ballotContract.getElectionStatus(1)).to.equal(false)
        })

        it("Should succesfully pick the correct winner and emit event", async function () {
            const candidate4 = "candidate 4"

            const ballotContractAddress = ballotContract.target
            VotingContract = await ethers.getContractFactory("VotingContract")
            votingContract = await VotingContract.deploy(ballotContractAddress)

            await ballotContract.addElection(
                [candidate1, candidate2, candidate3, candidate4],
                electionTimeLimit,
            )

            await votingContract.connect(addr1).addVotes([0, 1, 2, 3], 2)
            await votingContract.connect(addr3).addVotes([2, 3, 0, 1], 2)
            await votingContract.connect(addr4).addVotes([1, 2, 3, 0], 2)
            await votingContract.connect(addr2).addVotes([1, 2, 3, 0], 2)

            await time.increase(electionTimeLimit * 24 * 60 * 60)

            const closedElection = await ballotContract.closeElection(2)
            await expect(closedElection).to.emit(ballotContract, "TheWinnerIs")
            await closedElection.wait()

            expect(await ballotContract.getElectionWinner(2)).to.equal(
                candidate2,
            )
        })

        it("Should pick the correct winner if first votes are tied", async function () {
            const ballotContractAddress = ballotContract.target
            VotingContract = await ethers.getContractFactory("VotingContract")
            votingContract = await VotingContract.deploy(ballotContractAddress)

            await ballotContract.addElection(
                [candidate1, candidate2, candidate3],
                electionTimeLimit,
            )

            await votingContract.connect(addr1).addVotes([0, 1, 2], 2)
            await votingContract.connect(addr2).addVotes([0, 1, 2], 2)
            await votingContract.connect(addr3).addVotes([1, 0, 2], 2)
            await votingContract.connect(addr4).addVotes([1, 2, 0], 2)

            await time.increase(electionTimeLimit * 24 * 60 * 60)

            const closedElection = await ballotContract.closeElection(2)
            await expect(closedElection).to.emit(ballotContract, "TheWinnerIs")
            await closedElection.wait()

            expect(await ballotContract.getElectionWinner(2)).to.equal(
                candidate2,
            )
        })

        it("Should pick the correct winner if second votes are tied", async function () {
            const candidate4 = "candidate 4"

            const ballotContractAddress = ballotContract.target
            VotingContract = await ethers.getContractFactory("VotingContract")
            votingContract = await VotingContract.deploy(ballotContractAddress)

            await ballotContract.addElection(
                [candidate1, candidate2, candidate3, candidate4],
                electionTimeLimit,
            )

            await votingContract.connect(addr1).addVotes([0, 1, 2, 3], 2)
            await votingContract.connect(addr2).addVotes([0, 1, 2, 3], 2)
            await votingContract.connect(addr3).addVotes([1, 0, 2, 3], 2)
            await votingContract.connect(addr4).addVotes([1, 0, 2, 3], 2)
            await votingContract.connect(addr5).addVotes([3, 2, 0, 1], 2)

            await time.increase(electionTimeLimit * 24 * 60 * 60)

            const closedElection = await ballotContract.closeElection(2)
            await expect(closedElection).to.emit(ballotContract, "TheWinnerIs")
            await closedElection.wait()

            expect(await ballotContract.getElectionWinner(2)).to.equal(
                candidate1,
            )
        })

        it("Should emit an ElectionClosed event", async function () {
            await expect(ballotContract.closeElection(1)).to.emit(
                ballotContract,
                "ElectionClosed",
            )
        })

        it("Should revert if time is not up", async function () {
            await ballotContract.addElection(
                [candidate1, candidate2, candidate3],
                electionTimeLimit,
            )
            await expect(ballotContract.closeElection(2)).to.be.revertedWith(
                "Election is still open",
            )
        })

        it("Adds electionId to closedElections array", async function () {
            const closedElection = await ballotContract.closeElection(1)
            await closedElection.wait()
            expect(await ballotContract.getClosedElections()).to.deep.equal([1])
        })

        it("Removes electionId from openElections array", async function () {
            const closedElection = await ballotContract.closeElection(1)
            await closedElection.wait()
            expect(await ballotContract.getOpenElections()).to.deep.equal([])
        })
    })
})

Last updated