Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
224 lines
7.3 KiB
Solidity
224 lines
7.3 KiB
Solidity
|
|
// 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
|
|
);
|
|
}
|
|
}
|