I'm developing a voting contract. When I try to test the "vote" function, an error is thrown, I can’t understand what’s wrong.
After this line votingContract = await voting.connect(player)
of the last test
an error is thrown:
1) Voting Unit Tests
vote
reverts when a voter already voted:
Error: VM Exception while processing transaction: reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)
at Voting.vote (contracts/Voting.sol:100)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at runNextTicks (node:internal/process/task_queues:64:3)
at listOnTimeout (node:internal/timers:533:9)
at processTimers (node:internal/timers:507:7)
Voting.sol
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@chainlink/contracts/src/v0.8/AutomationCompatible.sol";
error Voting__NotAuthorized();
error Voting__AlreadyVoted();
error Voting__AlreadyRegistered();
error Voting__UnknownCandidate();
error Voting__WrongState();
error Voting__UpkeepNotNeeded();
error Voting__NoOneVoted();
contract Voting is Ownable, AutomationCompatibleInterface {
enum VotingState {
REG,
VOTING,
CALC
}
struct Voter {
bool authorized;
bool voted;
}
struct Candidate {
uint256 votes;
}
mapping(address => Voter) public voters;
Candidate[] public candidates;
address[] private votedList;
VotingState private state;
//uint256 public candidatesCount;
uint256 private registerTime;
uint256 private votingTime;
uint256 private lastTimeStamp;
event VoterVoted(address indexed voter, uint256 numCandidate);
event VoterRegistered(address indexed voter);
constructor(uint256 _candidatesCount, uint256 _registerTime, uint256 _votingTime) {
//candidatesCount = _candidatesCount;
registerTime = _registerTime;
votingTime = _votingTime;
for (uint256 i = 0; i < _candidatesCount; i++) {
candidates.push(Candidate({votes: 0}));
}
state = VotingState.REG;
lastTimeStamp = block.timestamp;
}
modifier onlyState(VotingState _state) {
if (state != _state) {
revert Voting__WrongState();
}
_;
}
function checkUpkeep(
bytes memory /* checkData */
) public view override returns (bool upkeepNeeded, bytes memory /* performData */) {
uint256 timeDiff = block.timestamp - lastTimeStamp;
upkeepNeeded = timeDiff > registerTime || timeDiff > votingTime;
}
function performUpkeep(bytes calldata /* performData */) public override {
(bool upkeepNeeded, ) = checkUpkeep("");
if (!upkeepNeeded) {
revert Voting__UpkeepNotNeeded();
}
if (state == VotingState.REG) {
state = VotingState.VOTING;
} else if (state == VotingState.VOTING) {
state = VotingState.CALC;
}
lastTimeStamp = block.timestamp;
}
function vote(uint256 _numCandidate) public onlyState(VotingState.VOTING) {
if (voters[msg.sender].authorized == false) {
revert Voting__NotAuthorized();
}
if (voters[msg.sender].voted) {
revert Voting__AlreadyVoted();
}
if (_numCandidate - 1 > getCandidatesCount()) {
revert Voting__UnknownCandidate();
}
candidates[_numCandidate].votes++; //uncrease count of votes
voters[msg.sender].voted = true; //mark that this user voted
votedList.push(msg.sender); //remember the voter
emit VoterVoted(msg.sender, _numCandidate);
}
function register(address _voter) public onlyOwner onlyState(VotingState.REG) {
if (voters[_voter].authorized) {
revert Voting__AlreadyRegistered();
}
voters[_voter].authorized = true; //register an user
voters[_voter].voted = false;
emit VoterRegistered(_voter);
}
function getWinner() public view onlyState(VotingState.CALC) returns (uint256) {
if (votedList.length <= 0) {
revert Voting__NoOneVoted();
}
uint256 maxVotes = 0;
uint256 numCandidate = 0;
for (uint256 i = 0; i < getCandidatesCount(); i++) {
if (candidates[i].votes > maxVotes) {
maxVotes = candidates[i].votes;
numCandidate = i;
}
}
return numCandidate;
}
function getRegisterTime() public view returns (uint256) {
return registerTime;
}
function getVotingTime() public view returns (uint256) {
return votingTime;
}
function getState() public view returns (VotingState) {
return state;
}
function getCandidatesCount() public view returns (uint256) {
return candidates.length;
}
function getVotedCount() public view returns (uint256) {
return votedList.length;
}
function getVoterAuthorized(address _voter) public view returns (bool) {
return voters[_voter].authorized;
}
function getVoterVoted(address _voter) public view returns (bool) {
return voters[_voter].voted;
}
}
Voting.test.js
const { assert, expect } = require("chai")
const { network, deployments, ethers, getNamedAccounts } = require("hardhat")
const { developmentChains, networkConfig } = require("../../helper-hardhat-config")
!developmentChains.includes(network.name)
? describe.skip
: describe("Voting Unit Tests", function () {
let accounts, voting, votingContract, intervalRegister, intervalVoting, player, deployer //deployer
beforeEach(async () => {
accounts = await ethers.getSigners()
deployer = accounts[0]
player = accounts[1] //deployer accounts[0]
await deployments.fixture(["voting"]) //deploy before test
voting = await ethers.getContract("Voting")
intervalRegister = await voting.getRegisterTime()
intervalVoting = await voting.getVotingTime() //60
})
describe("constructor", function () {
it("initializes the voting correctly", async function () {
const votingState = (await voting.getState()).toString()
assert.equal(votingState, "0")
const candidatesCount = (await voting.getCandidatesCount()).toString()
const expectedCandidatesCount =
networkConfig[network.config.chainId]["candidatesCount"]
assert.equal(candidatesCount, expectedCandidatesCount)
//60
const expectedintervalRegister =
networkConfig[network.config.chainId]["intervalRegister"]
assert.equal(intervalRegister.toString(), expectedintervalRegister)
const expectedintervalVoting =
networkConfig[network.config.chainId]["intervalRegister"]
assert.equal(intervalVoting.toString(), expectedintervalVoting)
})
})
describe("register", function () {
it("can register a voter", async function () {
//votingContract = await voting.connect(deployer)
await voting.register(player.address)
//const txReceipt = await txResponse.wait(1)
const registered = await voting.getVoterAuthorized(player.address)
assert.equal(registered, true)
const voted = await voting.getVoterVoted(player.address)
assert.equal(voted, false)
})
it("emits the event", async function () {
await expect(voting.register(player.address)).to.emit(voting, "VoterRegistered")
})
it("reverts when the state isn't correct", async function () {
await network.provider.send("evm_increaseTime", [intervalRegister.toNumber() + 1])
await network.provider.request({ method: "evm_mine", params: [] })
await voting.performUpkeep([])
await expect(voting.register(player.address)).to.be.revertedWith(
"Voting__WrongState"
)
})
it("only owner can register voters", async function () {
//вызвать функцию регистрации и попробовать передать адресс
votingContract = await voting.connect(player) //привязка игрока к контракту
const person = accounts[0].address
await expect(votingContract.register(person)).to.be.revertedWith(
"Ownable: caller is not the owner"
)
})
it("user can be registered only once", async function () {
await voting.register(player.address)
await expect(voting.register(player.address)).to.be.revertedWith(
"Voting__AlreadyRegistered"
)
})
})
describe("vote", function () {
// beforeEach(async () => {
// await voting.register(player.address)
// await network.provider.send("evm_increaseTime", [intervalRegister.toNumber() + 2])
// await network.provider.request({ method: "evm_mine", params: [] })
// await voting.performUpkeep([])
// })
it("reverts when the state isn't correct", async function () {
await voting.register(player.address)
votingContract = await voting.connect(player)
await expect(votingContract.vote(0)).to.be.revertedWith("Voting__WrongState")
})
it("reverts when a voter doesn't authorized", async function () {
//await voting.register(player.address)
await network.provider.send("evm_increaseTime", [intervalRegister.toNumber() + 1])
await network.provider.request({ method: "evm_mine", params: [] })
await voting.performUpkeep([])
votingContract = await voting.connect(player)
await expect(votingContract.vote(0)).to.be.revertedWith("Voting__NotAuthorized")
})
it("reverts when a voter already voted", async function () {
await voting.register(player.address)
await network.provider.send("evm_increaseTime", [intervalRegister.toNumber() + 1])
await network.provider.request({ method: "evm_mine", params: [] })
await voting.performUpkeep([])
votingContract = await voting.connect(player)
await votingContract.vote(0)
await expect(votingContract.vote(0)).to.be.revertedWith("Voting__AlreadyVoted")
})
})
})
Best Answer
In the failing test you call
So the contract will revert here
because in the call
vote(uint256 _numCandidate)
_numCandidate
is 0, and0 - 1
is overflow foruint256
.