704 lines
20 KiB
Plaintext
704 lines
20 KiB
Plaintext
///! # 提案管理系统
|
||
///!
|
||
///! Proposal Management System
|
||
///! 提供提案创建、执行和历史管理功能
|
||
///!
|
||
///! **版本**: v1.0
|
||
///! **模块**: charter-std/governance/proposal.ch
|
||
|
||
use utils::math::{safe_add, safe_sub};
|
||
use utils::crypto::sha3_384_hash;
|
||
|
||
// ============================================================================
|
||
// 提案枚举
|
||
// ============================================================================
|
||
|
||
/// 提案类型
|
||
pub enum ProposalType {
|
||
/// 参数修改
|
||
ParameterChange,
|
||
|
||
/// 资金支出
|
||
Treasury,
|
||
|
||
/// 合约升级
|
||
Upgrade,
|
||
|
||
/// 文本提案
|
||
Text,
|
||
|
||
/// 自定义
|
||
Custom
|
||
}
|
||
|
||
/// 提案状态
|
||
pub enum ProposalStatus {
|
||
/// 草稿
|
||
Draft,
|
||
|
||
/// 待投票
|
||
Pending,
|
||
|
||
/// 投票中
|
||
Active,
|
||
|
||
/// 已通过
|
||
Passed,
|
||
|
||
/// 未通过
|
||
Rejected,
|
||
|
||
/// 已执行
|
||
Executed,
|
||
|
||
/// 已取消
|
||
Cancelled,
|
||
|
||
/// 已过期
|
||
Expired
|
||
}
|
||
|
||
// ============================================================================
|
||
// 提案结构
|
||
// ============================================================================
|
||
|
||
/// 提案
|
||
struct Proposal {
|
||
/// 提案ID
|
||
proposal_id: Hash,
|
||
|
||
/// 提案类型
|
||
proposal_type: ProposalType,
|
||
|
||
/// 标题
|
||
title: String,
|
||
|
||
/// 描述
|
||
description: String,
|
||
|
||
/// 提案者
|
||
proposer: Address,
|
||
|
||
/// 创建时间
|
||
created_at: Timestamp,
|
||
|
||
/// 投票开始时间
|
||
voting_start: Timestamp,
|
||
|
||
/// 投票结束时间
|
||
voting_end: Timestamp,
|
||
|
||
/// 执行延迟(秒)
|
||
execution_delay: Duration,
|
||
|
||
/// 最早执行时间
|
||
earliest_execution: Timestamp,
|
||
|
||
/// 执行截止时间
|
||
execution_deadline: Timestamp,
|
||
|
||
/// 提案状态
|
||
status: ProposalStatus,
|
||
|
||
/// 投票ID
|
||
vote_id: Option<Hash>,
|
||
|
||
/// 执行交易哈希
|
||
execution_tx: Option<Hash>,
|
||
|
||
/// 执行时间
|
||
executed_at: Option<Timestamp>,
|
||
|
||
/// 取消原因
|
||
cancellation_reason: Option<String>
|
||
}
|
||
|
||
/// 提案操作
|
||
struct ProposalAction {
|
||
/// 目标合约
|
||
target: Address,
|
||
|
||
/// 函数签名
|
||
function_sig: String,
|
||
|
||
/// 调用数据
|
||
call_data: Bytes,
|
||
|
||
/// 转账金额
|
||
value: u256,
|
||
|
||
/// 描述
|
||
description: String
|
||
}
|
||
|
||
/// 提案元数据
|
||
struct ProposalMetadata {
|
||
/// 提案ID
|
||
proposal_id: Hash,
|
||
|
||
/// 标签
|
||
tags: Vec<String>,
|
||
|
||
/// 相关链接
|
||
links: Vec<String>,
|
||
|
||
/// 讨论链接
|
||
discussion_url: Option<String>,
|
||
|
||
/// 文档哈希
|
||
document_hash: Option<Hash>
|
||
}
|
||
|
||
// ============================================================================
|
||
// 提案事件
|
||
// ============================================================================
|
||
|
||
/// 提案创建事件
|
||
event ProposalCreated {
|
||
proposal_id: Hash,
|
||
proposer: Address,
|
||
title: String,
|
||
proposal_type: ProposalType,
|
||
timestamp: Timestamp
|
||
}
|
||
|
||
/// 提案提交事件
|
||
event ProposalSubmitted {
|
||
proposal_id: Hash,
|
||
vote_id: Hash,
|
||
voting_start: Timestamp,
|
||
voting_end: Timestamp,
|
||
timestamp: Timestamp
|
||
}
|
||
|
||
/// 提案通过事件
|
||
event ProposalPassed {
|
||
proposal_id: Hash,
|
||
vote_id: Hash,
|
||
timestamp: Timestamp
|
||
}
|
||
|
||
/// 提案拒绝事件
|
||
event ProposalRejected {
|
||
proposal_id: Hash,
|
||
vote_id: Hash,
|
||
timestamp: Timestamp
|
||
}
|
||
|
||
/// 提案执行事件
|
||
event ProposalExecuted {
|
||
proposal_id: Hash,
|
||
executor: Address,
|
||
execution_tx: Hash,
|
||
timestamp: Timestamp
|
||
}
|
||
|
||
/// 提案取消事件
|
||
event ProposalCancelled {
|
||
proposal_id: Hash,
|
||
canceller: Address,
|
||
reason: String,
|
||
timestamp: Timestamp
|
||
}
|
||
|
||
// ============================================================================
|
||
// 提案管理系统
|
||
// ============================================================================
|
||
|
||
/// 提案管理系统
|
||
certificate ProposalManagement {
|
||
/// 提案 (proposal_id => proposal)
|
||
let _proposals: Map<Hash, Proposal>;
|
||
|
||
/// 提案操作 (proposal_id => actions)
|
||
let _proposal_actions: Map<Hash, Vec<ProposalAction>>;
|
||
|
||
/// 提案元数据 (proposal_id => metadata)
|
||
let _proposal_metadata: Map<Hash, ProposalMetadata>;
|
||
|
||
/// 提案者提案列表 (proposer => proposal_ids)
|
||
let _proposer_proposals: Map<Address, Vec<Hash>>;
|
||
|
||
/// 投票系统地址
|
||
let _voting_system: Address;
|
||
|
||
/// 治理代币地址
|
||
let _governance_token: Address;
|
||
|
||
/// 时间锁地址
|
||
let _timelock: Address;
|
||
|
||
/// 管理员地址
|
||
let _admin: Address;
|
||
|
||
/// 提案门槛(需要的代币数量)
|
||
let _proposal_threshold: u256;
|
||
|
||
/// 默认投票期限(秒)
|
||
let _default_voting_period: Duration;
|
||
|
||
/// 默认执行延迟(秒)
|
||
let _default_execution_delay: Duration;
|
||
|
||
/// 执行窗口期(秒)
|
||
let _execution_window: Duration;
|
||
|
||
// ========== 构造函数 ==========
|
||
|
||
constructor(
|
||
voting_system: Address,
|
||
governance_token: Address,
|
||
timelock: Address,
|
||
proposal_threshold: u256,
|
||
voting_period: Duration,
|
||
execution_delay: Duration,
|
||
execution_window: Duration
|
||
) {
|
||
require(!voting_system.is_zero(), "Invalid voting system");
|
||
require(!governance_token.is_zero(), "Invalid governance token");
|
||
require(!timelock.is_zero(), "Invalid timelock");
|
||
require(proposal_threshold > 0, "Threshold must be positive");
|
||
require(voting_period > 0, "Voting period must be positive");
|
||
require(execution_delay > 0, "Execution delay must be positive");
|
||
require(execution_window > 0, "Execution window must be positive");
|
||
|
||
self._voting_system = voting_system;
|
||
self._governance_token = governance_token;
|
||
self._timelock = timelock;
|
||
self._admin = msg.sender;
|
||
self._proposal_threshold = proposal_threshold;
|
||
self._default_voting_period = voting_period;
|
||
self._default_execution_delay = execution_delay;
|
||
self._execution_window = execution_window;
|
||
}
|
||
|
||
// ========== 提案创建 ==========
|
||
|
||
/// 创建提案
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_type`: 提案类型
|
||
/// - `title`: 标题
|
||
/// - `description`: 描述
|
||
/// - `actions`: 操作列表
|
||
///
|
||
/// # 返回
|
||
/// - `Hash`: 提案ID
|
||
pub fn create_proposal(
|
||
proposal_type: ProposalType,
|
||
title: String,
|
||
description: String,
|
||
actions: Vec<ProposalAction>
|
||
) -> Hash {
|
||
require(!title.is_empty(), "Title required");
|
||
require(!description.is_empty(), "Description required");
|
||
|
||
// 检查提案门槛(实际需要查询治理代币余额)
|
||
let proposer_balance = self._get_token_balance(msg.sender);
|
||
require(
|
||
proposer_balance >= self._proposal_threshold,
|
||
"Insufficient tokens to propose"
|
||
);
|
||
|
||
// 生成提案ID
|
||
let proposal_id = self._generate_proposal_id(msg.sender, title.clone());
|
||
|
||
let proposal = Proposal {
|
||
proposal_id: proposal_id,
|
||
proposal_type: proposal_type,
|
||
title: title.clone(),
|
||
description: description,
|
||
proposer: msg.sender,
|
||
created_at: block.timestamp,
|
||
voting_start: 0,
|
||
voting_end: 0,
|
||
execution_delay: self._default_execution_delay,
|
||
earliest_execution: 0,
|
||
execution_deadline: 0,
|
||
status: ProposalStatus::Draft,
|
||
vote_id: None,
|
||
execution_tx: None,
|
||
executed_at: None,
|
||
cancellation_reason: None
|
||
};
|
||
|
||
self._proposals[proposal_id] = proposal;
|
||
self._proposal_actions[proposal_id] = actions;
|
||
|
||
// 添加到提案者列表
|
||
if !self._proposer_proposals.contains_key(msg.sender) {
|
||
self._proposer_proposals[msg.sender] = Vec::new();
|
||
}
|
||
self._proposer_proposals[msg.sender].push(proposal_id);
|
||
|
||
emit ProposalCreated {
|
||
proposal_id: proposal_id,
|
||
proposer: msg.sender,
|
||
title: title,
|
||
proposal_type: proposal_type,
|
||
timestamp: block.timestamp
|
||
};
|
||
|
||
return proposal_id;
|
||
}
|
||
|
||
/// 提交提案进行投票
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
/// - `voting_period`: 投票期限(秒,0表示使用默认值)
|
||
///
|
||
/// # 返回
|
||
/// - `Hash`: 投票ID
|
||
pub fn submit_proposal(
|
||
proposal_id: Hash,
|
||
voting_period: Duration
|
||
) -> Hash {
|
||
require(self._proposals.contains_key(proposal_id), "Proposal not found");
|
||
|
||
let mut proposal = self._proposals[proposal_id];
|
||
require(proposal.proposer == msg.sender, "Not the proposer");
|
||
require(proposal.status == ProposalStatus::Draft, "Not in draft status");
|
||
|
||
let period = if voting_period == 0 {
|
||
self._default_voting_period
|
||
} else {
|
||
voting_period
|
||
};
|
||
|
||
let voting_start = block.timestamp;
|
||
let voting_end = block.timestamp + period;
|
||
|
||
// 创建投票(实际需要调用投票系统合约)
|
||
let vote_id = self._create_vote(proposal_id, proposal.title.clone(), period);
|
||
|
||
proposal.voting_start = voting_start;
|
||
proposal.voting_end = voting_end;
|
||
proposal.status = ProposalStatus::Active;
|
||
proposal.vote_id = Some(vote_id);
|
||
|
||
// 计算执行时间窗口
|
||
proposal.earliest_execution = voting_end + proposal.execution_delay;
|
||
proposal.execution_deadline = proposal.earliest_execution + self._execution_window;
|
||
|
||
self._proposals[proposal_id] = proposal;
|
||
|
||
emit ProposalSubmitted {
|
||
proposal_id: proposal_id,
|
||
vote_id: vote_id,
|
||
voting_start: voting_start,
|
||
voting_end: voting_end,
|
||
timestamp: block.timestamp
|
||
};
|
||
|
||
return vote_id;
|
||
}
|
||
|
||
// ========== 提案执行 ==========
|
||
|
||
/// 执行提案
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
///
|
||
/// # 返回
|
||
/// - `bool`: 是否成功
|
||
pub fn execute_proposal(proposal_id: Hash) -> bool {
|
||
require(self._proposals.contains_key(proposal_id), "Proposal not found");
|
||
|
||
let mut proposal = self._proposals[proposal_id];
|
||
require(proposal.status == ProposalStatus::Passed, "Proposal not passed");
|
||
require(
|
||
block.timestamp >= proposal.earliest_execution,
|
||
"Execution delay not met"
|
||
);
|
||
require(
|
||
block.timestamp <= proposal.execution_deadline,
|
||
"Execution deadline passed"
|
||
);
|
||
|
||
// 获取提案操作
|
||
let actions = self._proposal_actions.get(proposal_id).unwrap_or(Vec::new());
|
||
|
||
// 执行所有操作(实际需要通过时间锁执行)
|
||
for action in actions {
|
||
self._execute_action(action);
|
||
}
|
||
|
||
// 生成执行交易哈希
|
||
let execution_tx = tx.hash;
|
||
|
||
proposal.status = ProposalStatus::Executed;
|
||
proposal.execution_tx = Some(execution_tx);
|
||
proposal.executed_at = Some(block.timestamp);
|
||
|
||
self._proposals[proposal_id] = proposal;
|
||
|
||
emit ProposalExecuted {
|
||
proposal_id: proposal_id,
|
||
executor: msg.sender,
|
||
execution_tx: execution_tx,
|
||
timestamp: block.timestamp
|
||
};
|
||
|
||
return true;
|
||
}
|
||
|
||
/// 更新提案状态(根据投票结果)
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
///
|
||
/// # 返回
|
||
/// - `bool`: 是否成功
|
||
pub fn update_proposal_status(proposal_id: Hash) -> bool {
|
||
require(self._proposals.contains_key(proposal_id), "Proposal not found");
|
||
|
||
let mut proposal = self._proposals[proposal_id];
|
||
require(proposal.status == ProposalStatus::Active, "Proposal not active");
|
||
require(block.timestamp >= proposal.voting_end, "Voting not ended");
|
||
|
||
// 获取投票结果(实际需要调用投票系统合约)
|
||
let vote_result = self._get_vote_result(proposal.vote_id.unwrap());
|
||
|
||
match vote_result {
|
||
VoteResult::Passed => {
|
||
proposal.status = ProposalStatus::Passed;
|
||
|
||
emit ProposalPassed {
|
||
proposal_id: proposal_id,
|
||
vote_id: proposal.vote_id.unwrap(),
|
||
timestamp: block.timestamp
|
||
};
|
||
},
|
||
_ => {
|
||
proposal.status = ProposalStatus::Rejected;
|
||
|
||
emit ProposalRejected {
|
||
proposal_id: proposal_id,
|
||
vote_id: proposal.vote_id.unwrap(),
|
||
timestamp: block.timestamp
|
||
};
|
||
}
|
||
}
|
||
|
||
self._proposals[proposal_id] = proposal;
|
||
|
||
return true;
|
||
}
|
||
|
||
/// 取消提案
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
/// - `reason`: 取消原因
|
||
///
|
||
/// # 返回
|
||
/// - `bool`: 是否成功
|
||
pub fn cancel_proposal(proposal_id: Hash, reason: String) -> bool {
|
||
require(self._proposals.contains_key(proposal_id), "Proposal not found");
|
||
|
||
let mut proposal = self._proposals[proposal_id];
|
||
require(
|
||
msg.sender == proposal.proposer || msg.sender == self._admin,
|
||
"Not authorized"
|
||
);
|
||
require(
|
||
proposal.status == ProposalStatus::Draft ||
|
||
proposal.status == ProposalStatus::Pending ||
|
||
proposal.status == ProposalStatus::Active,
|
||
"Cannot cancel"
|
||
);
|
||
|
||
proposal.status = ProposalStatus::Cancelled;
|
||
proposal.cancellation_reason = Some(reason.clone());
|
||
|
||
self._proposals[proposal_id] = proposal;
|
||
|
||
emit ProposalCancelled {
|
||
proposal_id: proposal_id,
|
||
canceller: msg.sender,
|
||
reason: reason,
|
||
timestamp: block.timestamp
|
||
};
|
||
|
||
return true;
|
||
}
|
||
|
||
// ========== 元数据管理 ==========
|
||
|
||
/// 设置提案元数据
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
/// - `tags`: 标签
|
||
/// - `links`: 相关链接
|
||
/// - `discussion_url`: 讨论链接
|
||
/// - `document_hash`: 文档哈希
|
||
///
|
||
/// # 返回
|
||
/// - `bool`: 是否成功
|
||
pub fn set_proposal_metadata(
|
||
proposal_id: Hash,
|
||
tags: Vec<String>,
|
||
links: Vec<String>,
|
||
discussion_url: Option<String>,
|
||
document_hash: Option<Hash>
|
||
) -> bool {
|
||
require(self._proposals.contains_key(proposal_id), "Proposal not found");
|
||
|
||
let proposal = self._proposals[proposal_id];
|
||
require(proposal.proposer == msg.sender, "Not the proposer");
|
||
|
||
let metadata = ProposalMetadata {
|
||
proposal_id: proposal_id,
|
||
tags: tags,
|
||
links: links,
|
||
discussion_url: discussion_url,
|
||
document_hash: document_hash
|
||
};
|
||
|
||
self._proposal_metadata[proposal_id] = metadata;
|
||
|
||
return true;
|
||
}
|
||
|
||
/// 获取提案元数据
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
///
|
||
/// # 返回
|
||
/// - `Option<ProposalMetadata>`: 元数据
|
||
pub fn get_proposal_metadata(proposal_id: Hash) -> Option<ProposalMetadata> {
|
||
return self._proposal_metadata.get(proposal_id);
|
||
}
|
||
|
||
// ========== 查询函数 ==========
|
||
|
||
/// 获取提案
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
///
|
||
/// # 返回
|
||
/// - `Proposal`: 提案信息
|
||
pub fn get_proposal(proposal_id: Hash) -> Proposal {
|
||
require(self._proposals.contains_key(proposal_id), "Proposal not found");
|
||
return self._proposals[proposal_id];
|
||
}
|
||
|
||
/// 获取提案操作
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
///
|
||
/// # 返回
|
||
/// - `Vec<ProposalAction>`: 操作列表
|
||
pub fn get_proposal_actions(proposal_id: Hash) -> Vec<ProposalAction> {
|
||
return self._proposal_actions.get(proposal_id).unwrap_or(Vec::new());
|
||
}
|
||
|
||
/// 获取提案者的提案列表
|
||
///
|
||
/// # 参数
|
||
/// - `proposer`: 提案者地址
|
||
///
|
||
/// # 返回
|
||
/// - `Vec<Hash>`: 提案ID列表
|
||
pub fn get_proposer_proposals(proposer: Address) -> Vec<Hash> {
|
||
return self._proposer_proposals.get(proposer).unwrap_or(Vec::new());
|
||
}
|
||
|
||
/// 检查提案是否可执行
|
||
///
|
||
/// # 参数
|
||
/// - `proposal_id`: 提案ID
|
||
///
|
||
/// # 返回
|
||
/// - `bool`: 是否可执行
|
||
pub fn is_executable(proposal_id: Hash) -> bool {
|
||
if !self._proposals.contains_key(proposal_id) {
|
||
return false;
|
||
}
|
||
|
||
let proposal = self._proposals[proposal_id];
|
||
|
||
return proposal.status == ProposalStatus::Passed &&
|
||
block.timestamp >= proposal.earliest_execution &&
|
||
block.timestamp <= proposal.execution_deadline;
|
||
}
|
||
|
||
// ========== 内部函数 ==========
|
||
|
||
/// 生成提案ID
|
||
fn _generate_proposal_id(proposer: Address, title: String) -> Hash {
|
||
let mut data = Bytes::new();
|
||
data.extend(proposer.as_bytes());
|
||
data.extend(title.as_bytes());
|
||
data.extend(block.timestamp.to_bytes());
|
||
data.extend(tx.hash.as_bytes());
|
||
return sha3_384_hash(data);
|
||
}
|
||
|
||
/// 获取代币余额(简化,实际需要调用代币合约)
|
||
fn _get_token_balance(account: Address) -> u256 {
|
||
return 1000; // 简化
|
||
}
|
||
|
||
/// 创建投票(简化,实际需要调用投票系统合约)
|
||
fn _create_vote(proposal_id: Hash, title: String, period: Duration) -> Hash {
|
||
// 实际需要调用投票系统的create_vote函数
|
||
return sha3_384_hash(proposal_id.as_bytes());
|
||
}
|
||
|
||
/// 获取投票结果(简化,实际需要调用投票系统合约)
|
||
fn _get_vote_result(vote_id: Hash) -> VoteResult {
|
||
// 实际需要调用投票系统的get_vote_result函数
|
||
return VoteResult::Passed;
|
||
}
|
||
|
||
/// 执行操作(简化,实际需要通过时间锁执行)
|
||
fn _execute_action(action: ProposalAction) {
|
||
// 实际需要通过时间锁合约执行
|
||
// timelock.execute(action.target, action.function_sig, action.call_data, action.value)
|
||
}
|
||
|
||
// ========== 管理函数 ==========
|
||
|
||
/// 设置提案门槛
|
||
pub fn set_proposal_threshold(threshold: u256) -> bool {
|
||
require(msg.sender == self._admin, "Only admin");
|
||
require(threshold > 0, "Threshold must be positive");
|
||
self._proposal_threshold = threshold;
|
||
return true;
|
||
}
|
||
|
||
/// 设置默认投票期限
|
||
pub fn set_default_voting_period(period: Duration) -> bool {
|
||
require(msg.sender == self._admin, "Only admin");
|
||
require(period > 0, "Period must be positive");
|
||
self._default_voting_period = period;
|
||
return true;
|
||
}
|
||
|
||
/// 设置默认执行延迟
|
||
pub fn set_default_execution_delay(delay: Duration) -> bool {
|
||
require(msg.sender == self._admin, "Only admin");
|
||
require(delay > 0, "Delay must be positive");
|
||
self._default_execution_delay = delay;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 投票结果枚举(从voting.ch引用)
|
||
// ============================================================================
|
||
|
||
enum VoteResult {
|
||
Undecided,
|
||
Passed,
|
||
Failed,
|
||
QuorumNotReached
|
||
}
|