| // 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;
    }
}
  |