Files
semaphore/test/SemaphoreVoting.ts
2022-05-31 18:22:30 +02:00

215 lines
8.9 KiB
TypeScript

import { Strategy, ZkIdentity } from "@zk-kit/identity"
import { Semaphore, SemaphorePublicSignals, SemaphoreSolidityProof } from "@zk-kit/protocols"
import { expect } from "chai"
import { Signer, utils } from "ethers"
import { ethers, run } from "hardhat"
import { SemaphoreVoting } from "../build/typechain"
import { createMerkleProof } from "./utils"
import { config } from "../package.json"
describe("SemaphoreVoting", () => {
let contract: SemaphoreVoting
let accounts: Signer[]
let coordinator: string
const depth = 20
const pollIds = [BigInt(1), BigInt(2), BigInt(3)]
const encryptionKey = BigInt(0)
const decryptionKey = BigInt(0)
const wasmFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.wasm`
const zkeyFilePath = `${config.paths.build["zk-files"]}/${depth}/semaphore.zkey`
before(async () => {
const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth })
contract = await run("deploy:semaphore-voting", { logs: false, verifier: verifierAddress })
accounts = await ethers.getSigners()
coordinator = await accounts[1].getAddress()
})
describe("# createPoll", () => {
it("Should not create a poll with a wrong depth", async () => {
const transaction = contract.createPoll(pollIds[0], coordinator, 10)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: depth value is not supported")
})
it("Should not create a poll greater than the snark scalar field", async () => {
const transaction = contract.createPoll(
BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495618"),
coordinator,
depth
)
await expect(transaction).to.be.revertedWith("SemaphoreGroups: group id must be < SNARK_SCALAR_FIELD")
})
it("Should create a poll", async () => {
const transaction = contract.createPoll(pollIds[0], coordinator, depth)
await expect(transaction).to.emit(contract, "PollCreated").withArgs(pollIds[0], coordinator)
})
it("Should not create a poll if it already exists", async () => {
const transaction = contract.createPoll(pollIds[0], coordinator, depth)
await expect(transaction).to.be.revertedWith("SemaphoreGroups: group already exists")
})
})
describe("# startPoll", () => {
it("Should not start the poll if the caller is not the coordinator", async () => {
const transaction = contract.startPoll(pollIds[0], encryptionKey)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator")
})
it("Should start the poll", async () => {
const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey)
await expect(transaction).to.emit(contract, "PollStarted").withArgs(pollIds[0], coordinator, encryptionKey)
})
it("Should not start a poll if it has already been started", async () => {
const transaction = contract.connect(accounts[1]).startPoll(pollIds[0], encryptionKey)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: poll has already been started")
})
})
describe("# addVoter", () => {
before(async () => {
await contract.createPoll(pollIds[1], coordinator, depth)
})
it("Should not add a voter if the caller is not the coordinator", async () => {
const identity = new ZkIdentity()
const identityCommitment = identity.genIdentityCommitment()
const transaction = contract.addVoter(pollIds[0], identityCommitment)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator")
})
it("Should not add a voter if the poll has already been started", async () => {
const identity = new ZkIdentity()
const identityCommitment = identity.genIdentityCommitment()
const transaction = contract.connect(accounts[1]).addVoter(pollIds[0], identityCommitment)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: voters can only be added before voting")
})
it("Should add a voter to an existing poll", async () => {
const identity = new ZkIdentity(Strategy.MESSAGE, "test")
const identityCommitment = identity.genIdentityCommitment()
const transaction = contract.connect(accounts[1]).addVoter(pollIds[1], identityCommitment)
await expect(transaction)
.to.emit(contract, "MemberAdded")
.withArgs(
pollIds[1],
identityCommitment,
"14787813191318312920980352979830075893203307366494541177071234930769373297362"
)
})
it("Should return the correct number of poll voters", async () => {
const size = await contract.getNumberOfLeaves(pollIds[1])
expect(size).to.be.eq(1)
})
})
describe("# castVote", () => {
const identity = new ZkIdentity(Strategy.MESSAGE, "test")
const identityCommitment = identity.genIdentityCommitment()
const merkleProof = createMerkleProof([identityCommitment, BigInt(1)], identityCommitment)
const vote = "1"
const bytes32Vote = utils.formatBytes32String(vote)
const witness = Semaphore.genWitness(
identity.getTrapdoor(),
identity.getNullifier(),
merkleProof,
pollIds[1],
vote
)
let solidityProof: SemaphoreSolidityProof
let publicSignals: SemaphorePublicSignals
before(async () => {
await contract.connect(accounts[1]).addVoter(pollIds[1], BigInt(1))
await contract.connect(accounts[1]).startPoll(pollIds[1], encryptionKey)
await contract.createPoll(pollIds[2], coordinator, depth)
const fullProof = await Semaphore.genProof(witness, wasmFilePath, zkeyFilePath)
publicSignals = fullProof.publicSignals
solidityProof = Semaphore.packToSolidityProof(fullProof.proof)
})
it("Should not cast a vote if the caller is not the coordinator", async () => {
const transaction = contract.castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[0], solidityProof)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator")
})
it("Should not cast a vote if the poll is not ongoing", async () => {
const transaction = contract
.connect(accounts[1])
.castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[2], solidityProof)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: vote can only be cast in an ongoing poll")
})
it("Should not cast a vote if the proof is not valid", async () => {
const nullifierHash = Semaphore.genNullifierHash(pollIds[0], identity.getNullifier())
const transaction = contract
.connect(accounts[1])
.castVote(bytes32Vote, nullifierHash, pollIds[1], solidityProof)
await expect(transaction).to.be.revertedWith("InvalidProof()")
})
it("Should cast a vote", async () => {
const transaction = contract
.connect(accounts[1])
.castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[1], solidityProof)
await expect(transaction).to.emit(contract, "VoteAdded").withArgs(pollIds[1], bytes32Vote)
})
it("Should not cast a vote twice", async () => {
const transaction = contract
.connect(accounts[1])
.castVote(bytes32Vote, publicSignals.nullifierHash, pollIds[1], solidityProof)
await expect(transaction).to.be.revertedWith("SemaphoreCore: you cannot use the same nullifier twice")
})
})
describe("# endPoll", () => {
it("Should not end the poll if the caller is not the coordinator", async () => {
const transaction = contract.endPoll(pollIds[1], decryptionKey)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: caller is not the poll coordinator")
})
it("Should end the poll", async () => {
const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey)
await expect(transaction).to.emit(contract, "PollEnded").withArgs(pollIds[1], coordinator, decryptionKey)
})
it("Should not end a poll if it has already been ended", async () => {
const transaction = contract.connect(accounts[1]).endPoll(pollIds[1], encryptionKey)
await expect(transaction).to.be.revertedWith("SemaphoreVoting: poll is not ongoing")
})
})
})