DreamGovernance.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

// Import the Verifier interface
import "./interfaces/IGovernanceVerifier.sol"; // Assuming interfaces folder exists

interface IGovernanceDreamNFT {
    function dreamLevel(uint256 tokenId) external view returns (uint256);
    function ownerOf(uint256 tokenId) external view returns (address);
    function balanceOf(address owner) external view returns (uint256);
}

/**
 * @title DreamGovernance
 * @notice Governance contract allowing DreamNFT holders (level 3+) to vote on proposals.
 *         Includes anonymous voting via Zero-Knowledge Proofs.
 */
contract DreamGovernance is Initializable, OwnableUpgradeable, UUPSUpgradeable {
    IGovernanceDreamNFT public dreamNFT;

    // Address of the deployed ZK Verifier contract for governance votes
    address public governanceVerifier;

    struct Proposal {
        string description;
        uint256 startBlock;
        uint256 endBlock;
        uint256 yesVotes; // Tracks total weight
        uint256 noVotes;  // Tracks total weight
        bool executed;
        mapping(address => bool) hasVoted; // Tracks non-anonymous votes by address
        mapping(bytes32 => bool) hasVotedAnonymously; // Tracks anonymous votes by unique identifier hash
    }

    mapping(uint256 => Proposal) public proposals;
    uint256 public nextProposalId;

    // Events
    event ProposalCreated(uint256 proposalId, string description, uint256 startBlock, uint256 endBlock);
    event Voted(uint256 proposalId, address voter, bool support, uint256 weight);
    event VotedAnonymously(uint256 proposalId, bytes32 indexed voterIdentifierHash, bool support, uint256 weight); // New event for ZKP votes
    event ProposalExecuted(uint256 proposalId, bool passed);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Initialize the contract
     * @param _dreamNFT Address of the DreamNFT contract
     */
    function initialize(address _dreamNFT) public initializer {
        require(_dreamNFT != address(0), "DreamGovernance: invalid NFT address");
        __Ownable_init(msg.sender);
        __UUPSUpgradeable_init();
        dreamNFT = IGovernanceDreamNFT(_dreamNFT);
    }

    /**
     * @notice Create a new proposal.
     */
    function createProposal(string memory _description, uint256 _votingPeriodInBlocks) external onlyOwner {
        require(_votingPeriodInBlocks > 0, "DreamGovernance: invalid voting period");
        uint256 proposalId = nextProposalId++;
        uint256 start = block.number;
        uint256 end = block.number + _votingPeriodInBlocks;

        // Initialize each field separately
        Proposal storage prop = proposals[proposalId];
        prop.description = _description;
        prop.startBlock = start;
        prop.endBlock = end;
        prop.yesVotes = 0;
        prop.noVotes = 0;
        prop.executed = false;
        // Mappings are implicitly initialized

        emit ProposalCreated(proposalId, _description, start, end);
    }

    /**
     * @notice Vote on a proposal directly, revealing the voter's address and tokens used.
     */
    function vote(uint256 proposalId, bool support, uint256[] calldata tokenIds) external {
        Proposal storage prop = proposals[proposalId];
        _checkCanVote(proposalId, msg.sender); // Reusable check

        uint256 totalWeight = 0;
        for (uint256 i = 0; i < tokenIds.length; i++) {
            // Verify ownership and calculate weight
            require(dreamNFT.ownerOf(tokenIds[i]) == msg.sender, "DreamGovernance: Not owner of specified token");
            uint256 level = dreamNFT.dreamLevel(tokenIds[i]);
            if (level >= 3) {
                 totalWeight += _calculateWeight(level);
            }
        }
        require(totalWeight > 0, "DreamGovernance: No voting power with specified tokens");

        prop.hasVoted[msg.sender] = true; // Mark address as voted

        _recordVote(proposalId, totalWeight, support);

        emit Voted(proposalId, msg.sender, support, totalWeight);
    }

     /**
     * @notice Vote on a proposal anonymously using a Zero-Knowledge Proof.
     * @param proposalId The ID of the proposal.
     * @param _proof The ZK proof generated off-chain.
     * @param _publicSignals Public signals from the ZK proof. Structure depends on the circuit.
     *                       Example: [proposalIdFromProof, voterIdentifierHash, votingWeight, voteSupport(0 or 1)]
     */
    function voteWithProof(
        uint256 proposalId,
        bytes calldata _proof,
        uint256[] calldata _publicSignals
    ) external {
        require(governanceVerifier != address(0), "DreamGovernance: Governance Verifier not set");
        require(_publicSignals.length >= 4, "DreamGovernance: Invalid public signals length"); // Basic check

        // Verify the proof
        bool verified = IGovernanceVerifier(governanceVerifier).verifyProof(_proof, _publicSignals);
        require(verified, "DreamGovernance: Invalid vote proof");

        // Extract data from public signals (adjust indices based on your circuit)
        uint256 proposalIdFromProof = _publicSignals[0];
        bytes32 voterIdentifierHash = bytes32(_publicSignals[1]); // Unique hash identifying the voter anonymously
        uint256 votingWeight = _publicSignals[2];
        bool support = _publicSignals[3] == 1; // Assuming 1 for yes, 0 for no

        // --- Perform checks ---
        require(proposalId == proposalIdFromProof, "DreamGovernance: Proof is for wrong proposal");
        require(votingWeight > 0, "DreamGovernance: Proof shows zero voting weight");

        Proposal storage prop = proposals[proposalId];

        // Check proposal status and if identifier has already voted
        _checkCanVoteAnonymous(proposalId, voterIdentifierHash);

        prop.hasVotedAnonymously[voterIdentifierHash] = true; // Mark identifier as voted

        _recordVote(proposalId, votingWeight, support);

        emit VotedAnonymously(proposalId, voterIdentifierHash, support, votingWeight);
    }


    /**
     * @notice Execute a proposal after voting ends.
     */
    function executeProposal(uint256 proposalId) external {
        Proposal storage prop = proposals[proposalId];
        require(!prop.executed, "DreamGovernance: Already executed");
        require(block.number > prop.endBlock, "DreamGovernance: Voting period not ended");

        bool passed = prop.yesVotes >= prop.noVotes; // Simple majority wins
        prop.executed = true;

        // --- Add proposal execution logic here ---
        // This part depends entirely on what the proposals are meant to do.
        // Examples:
        // - Call a function on another contract
        // - Change a parameter in this contract
        // - Transfer funds from a treasury
        // if (passed) {
        //     // execute action
        // }

        emit ProposalExecuted(proposalId, passed);
    }

    // --- Internal Helper Functions ---

    /**
     * @dev Common checks before allowing a direct vote.
     */
    function _checkCanVote(uint256 proposalId, address voter) internal view {
        Proposal storage prop = proposals[proposalId];
        require(block.number >= prop.startBlock && block.number <= prop.endBlock, "DreamGovernance: Not in voting window");
        require(!prop.hasVoted[voter], "DreamGovernance: Address already voted");
        require(!prop.executed, "DreamGovernance: Proposal already executed");
    }

    /**
     * @dev Common checks before allowing an anonymous vote.
     */
    function _checkCanVoteAnonymous(uint256 proposalId, bytes32 voterIdentifierHash) internal view {
        Proposal storage prop = proposals[proposalId];
        require(block.number >= prop.startBlock && block.number <= prop.endBlock, "DreamGovernance: Not in voting window");
        require(!prop.hasVotedAnonymously[voterIdentifierHash], "DreamGovernance: Identifier already voted");
        require(!prop.executed, "DreamGovernance: Proposal already executed");
    }

    /**
     * @dev Calculates voting weight based on NFT level.
     */
    function _calculateWeight(uint256 level) internal pure returns (uint256) {
        if (level == 3) return 1;
        if (level == 4) return 2;
        if (level == 5) return 4;
        return 0;
    }

    /**
     * @dev Records the vote weight in the proposal storage.
     */
    function _recordVote(uint256 proposalId, uint256 weight, bool support) internal {
         Proposal storage prop = proposals[proposalId];
         if (support) {
            prop.yesVotes += weight;
        } else {
            prop.noVotes += weight;
        }
    }

    // --- Admin Functions ---

    /**
     * @notice Sets the address of the ZK Verifier contract for governance votes.
     */
    function setGovernanceVerifier(address _verifier) external onlyOwner {
        require(_verifier != address(0), "DreamGovernance: Zero address for verifier");
        governanceVerifier = _verifier;
    }

    /**
     * @dev Authorization function for UUPS upgrades.
     */
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}