// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "./AereToken.sol"; /** * @title AERE Governance Contract * @dev Manages decentralized governance and proposals */ contract AereGovernance is Ownable, ReentrancyGuard { AereToken public aereToken; enum ProposalStatus { Active, Passed, Failed, Executed, Cancelled } enum VoteType { Yes, No, Abstain } struct Proposal { uint256 id; string title; string description; address proposer; uint256 startTime; uint256 endTime; uint256 yesVotes; uint256 noVotes; uint256 abstainVotes; ProposalStatus status; uint256 executionTime; bytes executionData; } struct Vote { VoteType voteType; uint256 votingPower; bool hasVoted; } // State variables mapping(uint256 => Proposal) public proposals; mapping(uint256 => mapping(address => Vote)) public votes; mapping(address => uint256) public votingPower; uint256 public proposalCount = 0; uint256 public constant VOTING_PERIOD = 7 days; uint256 public constant EXECUTION_DELAY = 2 days; uint256 public constant PROPOSAL_THRESHOLD = 100000 * 10**18; // 100K AERE uint256 public constant QUORUM_PERCENTAGE = 20; // 20% // Events event ProposalCreated(uint256 indexed proposalId, address indexed proposer, string title); event VoteCast(uint256 indexed proposalId, address indexed voter, VoteType voteType, uint256 votingPower); event ProposalExecuted(uint256 indexed proposalId); event ProposalCancelled(uint256 indexed proposalId); constructor(address _aereToken) { aereToken = AereToken(_aereToken); } /** * @dev Create new governance proposal */ function createProposal( string memory title, string memory description, bytes memory executionData ) external returns (uint256) { require(aereToken.balanceOf(msg.sender) >= PROPOSAL_THRESHOLD, "Insufficient tokens to propose"); proposalCount++; uint256 proposalId = proposalCount; proposals[proposalId] = Proposal({ id: proposalId, title: title, description: description, proposer: msg.sender, startTime: block.timestamp, endTime: block.timestamp + VOTING_PERIOD, yesVotes: 0, noVotes: 0, abstainVotes: 0, status: ProposalStatus.Active, executionTime: 0, executionData: executionData }); emit ProposalCreated(proposalId, msg.sender, title); return proposalId; } /** * @dev Cast vote on proposal */ function castVote(uint256 proposalId, VoteType voteType) external { Proposal storage proposal = proposals[proposalId]; require(proposal.status == ProposalStatus.Active, "Proposal not active"); require(block.timestamp <= proposal.endTime, "Voting period ended"); require(!votes[proposalId][msg.sender].hasVoted, "Already voted"); uint256 voterPower = aereToken.balanceOf(msg.sender); require(voterPower > 0, "No voting power"); votes[proposalId][msg.sender] = Vote({ voteType: voteType, votingPower: voterPower, hasVoted: true }); if (voteType == VoteType.Yes) { proposal.yesVotes += voterPower; } else if (voteType == VoteType.No) { proposal.noVotes += voterPower; } else { proposal.abstainVotes += voterPower; } emit VoteCast(proposalId, msg.sender, voteType, voterPower); } /** * @dev Finalize proposal after voting period */ function finalizeProposal(uint256 proposalId) external { Proposal storage proposal = proposals[proposalId]; require(proposal.status == ProposalStatus.Active, "Proposal not active"); require(block.timestamp > proposal.endTime, "Voting still active"); uint256 totalVotes = proposal.yesVotes + proposal.noVotes + proposal.abstainVotes; uint256 quorumRequired = (aereToken.getCirculatingSupply() * QUORUM_PERCENTAGE) / 100; if (totalVotes >= quorumRequired && proposal.yesVotes > proposal.noVotes) { proposal.status = ProposalStatus.Passed; proposal.executionTime = block.timestamp + EXECUTION_DELAY; } else { proposal.status = ProposalStatus.Failed; } } /** * @dev Execute passed proposal */ function executeProposal(uint256 proposalId) external { Proposal storage proposal = proposals[proposalId]; require(proposal.status == ProposalStatus.Passed, "Proposal not passed"); require(block.timestamp >= proposal.executionTime, "Execution delay not met"); proposal.status = ProposalStatus.Executed; // Execute proposal logic here if (proposal.executionData.length > 0) { (bool success,) = address(this).call(proposal.executionData); require(success, "Execution failed"); } emit ProposalExecuted(proposalId); } /** * @dev Cancel proposal (only proposer or owner) */ function cancelProposal(uint256 proposalId) external { Proposal storage proposal = proposals[proposalId]; require( msg.sender == proposal.proposer || msg.sender == owner(), "Not authorized to cancel" ); require(proposal.status == ProposalStatus.Active, "Proposal not active"); proposal.status = ProposalStatus.Cancelled; emit ProposalCancelled(proposalId); } /** * @dev Get proposal details */ function getProposal(uint256 proposalId) external view returns (Proposal memory) { return proposals[proposalId]; } /** * @dev Get vote details */ function getVote(uint256 proposalId, address voter) external view returns (Vote memory) { return votes[proposalId][voter]; } /** * @dev Check if proposal has quorum */ function hasQuorum(uint256 proposalId) external view returns (bool) { Proposal memory proposal = proposals[proposalId]; uint256 totalVotes = proposal.yesVotes + proposal.noVotes + proposal.abstainVotes; uint256 quorumRequired = (aereToken.getCirculatingSupply() * QUORUM_PERCENTAGE) / 100; return totalVotes >= quorumRequired; } /** * @dev Get governance statistics */ function getGovernanceStats() external view returns ( uint256 _totalProposals, uint256 _activeProposals, uint256 _totalVoters, uint256 _quorumPercentage ) { uint256 activeCount = 0; for (uint256 i = 1; i <= proposalCount; i++) { if (proposals[i].status == ProposalStatus.Active) { activeCount++; } } return ( proposalCount, activeCount, aereToken.getCirculatingSupply() / (10**18), // Convert to readable number QUORUM_PERCENTAGE ); } }