gcx/blockchain/genex-contracts/src/Governance.sol

214 lines
7.5 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
/// @title Governance — 治理合约
/// @notice 3/5 多签 + 48h 时间锁 + 紧急 4h 通道 + 合约升级回滚
/// @dev 管理所有合约的升级、参数调整、紧急冻结
contract Governance is Initializable, AccessControlUpgradeable {
bytes32 public constant MULTISIG_ROLE = keccak256("MULTISIG_ROLE");
uint256 public constant REQUIRED_SIGNATURES = 3; // 3/5 多签
uint256 public constant EMERGENCY_SIGNATURES = 4; // 紧急需 4/5
uint256 public constant TIMELOCK = 48 hours; // 标准时间锁
uint256 public constant EMERGENCY_TIMELOCK = 4 hours; // 紧急时间锁
uint256 public nextProposalId;
struct Proposal {
bytes callData;
address target;
uint256 executeAfter;
uint256 approvalCount;
bool executed;
bool emergency;
address proposer;
string description;
uint256 createdAt;
}
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public approvals;
// 合约升级回滚支持
mapping(address => address) public previousImplementations;
// --- Events ---
event ProposalCreated(
uint256 indexed proposalId,
address indexed proposer,
address target,
bool emergency,
string description
);
event ProposalApproved(uint256 indexed proposalId, address indexed approver, uint256 approvalCount);
event ProposalExecuted(uint256 indexed proposalId, address indexed executor);
event ProposalCancelled(uint256 indexed proposalId);
event ContractRolledBack(address indexed proxy, address indexed previousImpl);
event PreviousImplementationRecorded(address indexed proxy, address indexed impl);
function initialize(address[] memory multisigMembers, address admin) external initializer {
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
for (uint256 i = 0; i < multisigMembers.length; i++) {
_grantRole(MULTISIG_ROLE, multisigMembers[i]);
}
}
// ========================
// 提案管理
// ========================
/// @notice 创建标准提案48h 时间锁)
function propose(
address target,
bytes calldata data,
string calldata description
) external onlyRole(MULTISIG_ROLE) returns (uint256 proposalId) {
proposalId = _createProposal(target, data, description, false);
}
/// @notice 创建紧急提案4h 时间锁)
function proposeEmergency(
address target,
bytes calldata data,
string calldata description
) external onlyRole(MULTISIG_ROLE) returns (uint256 proposalId) {
proposalId = _createProposal(target, data, description, true);
}
/// @notice 审批提案
function approve(uint256 proposalId) external onlyRole(MULTISIG_ROLE) {
Proposal storage p = proposals[proposalId];
require(p.target != address(0), "Governance: proposal not found");
require(!p.executed, "Governance: already executed");
require(!approvals[proposalId][msg.sender], "Governance: already approved");
approvals[proposalId][msg.sender] = true;
p.approvalCount++;
emit ProposalApproved(proposalId, msg.sender, p.approvalCount);
}
/// @notice 执行提案
function execute(uint256 proposalId) external onlyRole(MULTISIG_ROLE) {
Proposal storage p = proposals[proposalId];
require(p.target != address(0), "Governance: proposal not found");
require(!p.executed, "Governance: already executed");
// 签名数量检查
uint256 required = p.emergency ? EMERGENCY_SIGNATURES : REQUIRED_SIGNATURES;
require(p.approvalCount >= required, "Governance: not enough approvals");
// 时间锁检查
require(block.timestamp >= p.executeAfter, "Governance: timelock not expired");
p.executed = true;
(bool success,) = p.target.call(p.callData);
require(success, "Governance: execution failed");
emit ProposalExecuted(proposalId, msg.sender);
}
/// @notice 取消提案(仅提案者或 Admin
function cancel(uint256 proposalId) external {
Proposal storage p = proposals[proposalId];
require(p.target != address(0), "Governance: proposal not found");
require(!p.executed, "Governance: already executed");
require(
p.proposer == msg.sender || hasRole(DEFAULT_ADMIN_ROLE, msg.sender),
"Governance: not authorized"
);
// 标记为已执行来阻止未来执行
p.executed = true;
emit ProposalCancelled(proposalId);
}
// ========================
// 合约升级回滚
// ========================
/// @notice 升级时记录前一版本 Implementation 地址
function recordPreviousImplementation(address proxy, address currentImpl)
external
onlyRole(MULTISIG_ROLE)
{
previousImplementations[proxy] = currentImpl;
emit PreviousImplementationRecorded(proxy, currentImpl);
}
/// @notice 紧急回滚至上一版本(需 4/5 多签)
/// @dev 通过紧急提案调用此方法
function rollback(address proxy) external onlyRole(MULTISIG_ROLE) {
address prevImpl = previousImplementations[proxy];
require(prevImpl != address(0), "Governance: no previous version");
// 调用 proxy 的 upgradeTo
(bool success,) = proxy.call(
abi.encodeWithSignature("upgradeTo(address)", prevImpl)
);
require(success, "Governance: rollback failed");
emit ContractRolledBack(proxy, prevImpl);
}
// ========================
// 查询
// ========================
/// @notice 查询提案详情
function getProposal(uint256 proposalId) external view returns (
address target,
uint256 executeAfter,
uint256 approvalCount,
bool executed,
bool emergency,
address proposer,
string memory description
) {
Proposal storage p = proposals[proposalId];
return (p.target, p.executeAfter, p.approvalCount, p.executed, p.emergency, p.proposer, p.description);
}
/// @notice 查询某地址是否已审批
function hasApproved(uint256 proposalId, address member) external view returns (bool) {
return approvals[proposalId][member];
}
// ========================
// Internal
// ========================
function _createProposal(
address target,
bytes calldata data,
string calldata description,
bool emergency
) internal returns (uint256 proposalId) {
require(target != address(0), "Governance: zero target");
proposalId = nextProposalId++;
uint256 timelock = emergency ? EMERGENCY_TIMELOCK : TIMELOCK;
proposals[proposalId] = Proposal({
callData: data,
target: target,
executeAfter: block.timestamp + timelock,
approvalCount: 1, // 提案者自动审批
executed: false,
emergency: emergency,
proposer: msg.sender,
description: description,
createdAt: block.timestamp
});
approvals[proposalId][msg.sender] = true;
emit ProposalCreated(proposalId, msg.sender, target, emergency, description);
}
}