214 lines
7.5 KiB
Solidity
214 lines
7.5 KiB
Solidity
// 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);
|
||
}
|
||
}
|