// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Unirep} from '.././Unirep.sol';
// Uncomment this line to use console.log
// import 'hardhat/console.sol';
import {ReputationVerifierHelper} from '../verifierHelpers/ReputationVerifierHelper.sol';
import {EpochKeyVerifierHelper} from '../verifierHelpers/EpochKeyVerifierHelper.sol';
import {VotingPrizeNFT} from './VotingPrizeNFT.sol';
enum Option {
UP,
DOWN
}
/// @title UnirepVoting
contract UnirepVoting {
Unirep public unirep;
ReputationVerifierHelper public repHelper;
EpochKeyVerifierHelper public epochKeyHelper;
VotingPrizeNFT public nft;
uint public immutable numProjects;
// A list of [upvote count, downvote count] for each project.
uint[][] public projectData;
// A list of scores for each project that project ID is the index.
int[] public scores;
int public winnerScore;
bool foundWinner = false;
// A mapping from project ID to a list of epoch keys of joined hackers.
mapping(uint256 => uint256[]) public participants;
// A mapping from project ID to a count of joined hackers.
mapping(uint256 => uint256) public counts;
mapping(uint256 => uint256) public voted;
mapping(uint256 => bool) claimed;
constructor(
Unirep _unirep,
ReputationVerifierHelper _repHelper,
EpochKeyVerifierHelper _epochKeyHelper,
VotingPrizeNFT _nft,
uint8 _numProjects,
uint48 _epochLength
) {
// set unirep address
unirep = _unirep;
// set reputation verifier
repHelper = _repHelper;
// set epoch key verifier helper
epochKeyHelper = _epochKeyHelper;
nft = _nft;
unirep.attesterSignUp(_epochLength);
// how many projects that hackers can join.
numProjects = _numProjects;
scores = new int[](numProjects);
projectData = new uint[][](numProjects);
for (uint i; i < numProjects; i++) {
projectData[i] = new uint256[](2);
}
}
// sign up users in this app
function userSignUp(
uint256[] calldata publicSignals,
uint256[8] calldata proof
) public {
unirep.userSignUp(publicSignals, proof);
}
function joinProject(
uint256 projectID,
uint256[] memory publicSignals,
uint256[8] memory proof
) public {
require(projectID < numProjects, 'projectID out of range');
EpochKeyVerifierHelper.EpochKeySignals memory signals = epochKeyHelper
.verifyAndCheck(publicSignals, proof);
require(signals.revealNonce == true, 'Voteathon: should reveal nonce');
require(signals.nonce == 0, 'Voteathon: invalid nonce');
require(
counts[projectID] < 10,
'Voteathon: maximum participants in a project'
);
participants[projectID].push(signals.epochKey);
// give user data if there is attestation before
uint48 epoch = unirep.attesterCurrentEpoch(uint160(address(this)));
require(epoch == 0, 'Voteathon: not join epoch');
uint256[] memory data = projectData[projectID];
for (uint256 i = 0; i < data.length; i++) {
unirep.attest(signals.epochKey, epoch, i, data[i]);
}
counts[projectID] += 1;
}
function vote(
uint256 projectID,
Option option,
uint256[] calldata publicSignals,
uint256[8] calldata proof
) public {
require(projectID < numProjects, 'projectID out of range');
EpochKeyVerifierHelper.EpochKeySignals memory signals = epochKeyHelper
.verifyAndCheck(publicSignals, proof);
require(signals.revealNonce == true, 'reveal nonce wrong');
require(signals.nonce == 1, 'nonce wrong');
require(signals.epoch == 0, 'invalid epoch to vote');
projectData[projectID][uint(option)] += 1;
voted[signals.epochKey] += 1;
if (option == Option.UP) scores[projectID] += 1;
else if (option == Option.DOWN) scores[projectID] -= 1;
uint[] memory members = participants[projectID];
uint48 epoch = unirep.attesterCurrentEpoch(uint160(address(this)));
require(epoch == 0, 'not voting epoch');
for (uint256 i = 0; i < members.length; i++) {
unirep.attest(members[i], epoch, uint(option), 1);
}
}
function claimPrize(
address receiver,
uint256[] calldata publicSignals,
uint256[8] calldata proof
) public {
uint160 attesterId = uint160(address(this));
require(unirep.attesterCurrentEpoch(attesterId) > 0);
ReputationVerifierHelper.ReputationSignals memory signals = repHelper
.verifyAndCheck(publicSignals, proof);
require(signals.epoch > 0, 'invalid epoch to claim prize');
require(signals.revealNonce == true, 'reveal nonce wrong');
require(signals.nonce == 1, 'nonce wrong');
require(!claimed[signals.epochKey], 'Already claimed');
if (!foundWinner) {
_findWinner();
}
require(int(signals.minRep) >= winnerScore, 'Insufficient score');
nft.awardItem(receiver);
claimed[signals.epochKey] = true;
}
function _findWinner() internal {
int highest = 0;
for (uint256 i = 0; i < numProjects; i++) {
if (scores[i] > highest) {
highest = scores[i];
}
}
winnerScore = highest;
foundWinner = true;
}
}
|