feat: complete phase 3 developer ecosystem and dao governance

This commit is contained in:
oib
2026-02-28 23:24:26 +01:00
parent 5bc18d684c
commit d8a432ce33
9 changed files with 1341 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title DAOGovernance
* @dev Multi-jurisdictional DAO framework with regional councils and staking.
*/
contract DAOGovernance is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public governanceToken;
// Staking Parameters
uint256 public minStakeAmount;
uint256 public unbondingPeriod = 7 days;
struct Staker {
uint256 amount;
uint256 unbondingAmount;
uint256 unbondingCompleteTime;
uint256 lastStakeTime;
}
mapping(address => Staker) public stakers;
uint256 public totalStaked;
// Proposal Parameters
enum ProposalState { Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired, Executed }
struct Proposal {
uint256 id;
address proposer;
string region; // "" for global
string descriptionHash;
uint256 forVotes;
uint256 againstVotes;
uint256 startTime;
uint256 endTime;
bool executed;
bool canceled;
mapping(address => bool) hasVoted;
}
uint256 public proposalCount;
mapping(uint256 => Proposal) public proposals;
// Regional Councils
mapping(string => mapping(address => bool)) public isRegionalCouncilMember;
mapping(string => address[]) public regionalCouncilMembers;
// Events
event Staked(address indexed user, uint256 amount);
event Unstaked(address indexed user, uint256 amount);
event ProposalCreated(uint256 indexed id, address proposer, string region, string descriptionHash);
event VoteCast(address indexed voter, uint256 indexed proposalId, bool support, uint256 weight);
event ProposalExecuted(uint256 indexed id);
constructor(address _governanceToken, uint256 _minStakeAmount) {
governanceToken = IERC20(_governanceToken);
minStakeAmount = _minStakeAmount;
}
// --- Staking ---
function stake(uint256 _amount) external nonReentrant {
require(_amount > 0, "Cannot stake 0");
governanceToken.safeTransferFrom(msg.sender, address(this), _amount);
stakers[msg.sender].amount += _amount;
stakers[msg.sender].lastStakeTime = block.timestamp;
totalStaked += _amount;
require(stakers[msg.sender].amount >= minStakeAmount, "Below min stake");
emit Staked(msg.sender, _amount);
}
function initiateUnstake(uint256 _amount) external nonReentrant {
Staker storage staker = stakers[msg.sender];
require(_amount > 0 && staker.amount >= _amount, "Invalid amount");
require(staker.unbondingAmount == 0, "Unbonding already in progress");
staker.amount -= _amount;
staker.unbondingAmount = _amount;
staker.unbondingCompleteTime = block.timestamp + unbondingPeriod;
totalStaked -= _amount;
}
function completeUnstake() external nonReentrant {
Staker storage staker = stakers[msg.sender];
require(staker.unbondingAmount > 0, "Nothing to unstake");
require(block.timestamp >= staker.unbondingCompleteTime, "Unbonding not complete");
uint256 amount = staker.unbondingAmount;
staker.unbondingAmount = 0;
governanceToken.safeTransfer(msg.sender, amount);
emit Unstaked(msg.sender, amount);
}
// --- Proposals & Voting ---
function createProposal(string calldata _region, string calldata _descriptionHash, uint256 _votingPeriod) external returns (uint256) {
require(stakers[msg.sender].amount >= minStakeAmount, "Must be staked to propose");
// If regional, must be a council member
if (bytes(_region).length > 0) {
require(isRegionalCouncilMember[_region][msg.sender], "Not a council member");
}
proposalCount++;
Proposal storage p = proposals[proposalCount];
p.id = proposalCount;
p.proposer = msg.sender;
p.region = _region;
p.descriptionHash = _descriptionHash;
p.startTime = block.timestamp;
p.endTime = block.timestamp + _votingPeriod;
emit ProposalCreated(p.id, msg.sender, _region, _descriptionHash);
return p.id;
}
function castVote(uint256 _proposalId, bool _support) external {
Proposal storage p = proposals[_proposalId];
require(block.timestamp >= p.startTime && block.timestamp <= p.endTime, "Voting closed");
require(!p.hasVoted[msg.sender], "Already voted");
uint256 weight = stakers[msg.sender].amount;
require(weight > 0, "No voting weight");
// If regional, must be a council member
if (bytes(p.region).length > 0) {
require(isRegionalCouncilMember[p.region][msg.sender], "Not a council member");
weight = 1; // 1 member = 1 vote in council
}
p.hasVoted[msg.sender] = true;
if (_support) {
p.forVotes += weight;
} else {
p.againstVotes += weight;
}
emit VoteCast(msg.sender, _proposalId, _support, weight);
}
function executeProposal(uint256 _proposalId) external nonReentrant {
Proposal storage p = proposals[_proposalId];
require(block.timestamp > p.endTime, "Voting not ended");
require(!p.executed && !p.canceled, "Already executed or canceled");
require(p.forVotes > p.againstVotes, "Proposal defeated");
p.executed = true;
// The actual execution logic (e.g., transferring treasury funds) would happen here
// Usually involves calling other contracts via target[] and callData[] arrays.
emit ProposalExecuted(_proposalId);
}
// --- Admin Functions ---
function setRegionalCouncilMember(string calldata _region, address _member, bool _status) external onlyOwner {
isRegionalCouncilMember[_region][_member] = _status;
if (_status) {
regionalCouncilMembers[_region].push(_member);
}
// Simplified array management for hackathon/demo purposes
}
}