580 lines
16 KiB
Rust
580 lines
16 KiB
Rust
// ACC-Governance: 资产治理协议(Governance Protocol)
|
||
//
|
||
// NAC原生的资产治理标准,用于去中心化治理和投票
|
||
// 100% NAC原生协议,不是任何现有标准的实现
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// 提案状态
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum ProposalStatus {
|
||
/// 草稿
|
||
Draft,
|
||
/// 活跃(投票中)
|
||
Active,
|
||
/// 已通过
|
||
Passed,
|
||
/// 已拒绝
|
||
Rejected,
|
||
/// 已执行
|
||
Executed,
|
||
/// 已取消
|
||
Cancelled,
|
||
}
|
||
|
||
/// 投票选项
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum VoteOption {
|
||
/// 赞成
|
||
For,
|
||
/// 反对
|
||
Against,
|
||
/// 弃权
|
||
Abstain,
|
||
}
|
||
|
||
/// 提案类型
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum ProposalType {
|
||
/// 参数修改
|
||
ParameterChange,
|
||
/// 资金分配
|
||
FundingAllocation,
|
||
/// 协议升级
|
||
ProtocolUpgrade,
|
||
/// 资产管理
|
||
AssetManagement,
|
||
/// 其他
|
||
Other(String),
|
||
}
|
||
|
||
/// 治理提案
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct GovernanceProposal {
|
||
/// 提案ID
|
||
pub proposal_id: String,
|
||
/// 资产ID
|
||
pub asset_id: String,
|
||
/// 提案人地址
|
||
pub proposer: String,
|
||
/// 提案类型
|
||
pub proposal_type: ProposalType,
|
||
/// 提案标题
|
||
pub title: String,
|
||
/// 提案描述
|
||
pub description: String,
|
||
/// 提案状态
|
||
pub status: ProposalStatus,
|
||
/// 创建时间
|
||
pub created_at: u64,
|
||
/// 投票开始时间
|
||
pub voting_start: u64,
|
||
/// 投票结束时间
|
||
pub voting_end: u64,
|
||
/// 执行时间
|
||
pub executed_at: Option<u64>,
|
||
/// 赞成票数
|
||
pub votes_for: u128,
|
||
/// 反对票数
|
||
pub votes_against: u128,
|
||
/// 弃权票数
|
||
pub votes_abstain: u128,
|
||
/// 通过阈值(百分比,如5000表示50%)
|
||
pub quorum_threshold: u16,
|
||
/// 投票记录 (voter -> VoteOption)
|
||
pub votes: HashMap<String, VoteOption>,
|
||
}
|
||
|
||
/// 投票记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct VoteRecord {
|
||
/// 投票ID
|
||
pub vote_id: String,
|
||
/// 提案ID
|
||
pub proposal_id: String,
|
||
/// 投票人地址
|
||
pub voter: String,
|
||
/// 投票选项
|
||
pub option: VoteOption,
|
||
/// 投票权重
|
||
pub voting_power: u128,
|
||
/// 投票时间
|
||
pub voted_at: u64,
|
||
}
|
||
|
||
/// 治理错误类型
|
||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum GovernanceError {
|
||
/// 提案不存在
|
||
ProposalNotFound,
|
||
/// 提案已存在
|
||
ProposalAlreadyExists,
|
||
/// 投票未开始
|
||
VotingNotStarted,
|
||
/// 投票已结束
|
||
VotingEnded,
|
||
/// 已投票
|
||
AlreadyVoted,
|
||
/// 提案未通过
|
||
ProposalNotPassed,
|
||
/// 提案已执行
|
||
ProposalAlreadyExecuted,
|
||
/// 未授权操作
|
||
Unauthorized,
|
||
/// 无效的投票权重
|
||
InvalidVotingPower,
|
||
}
|
||
|
||
/// 治理状态
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct GovernanceState {
|
||
/// 提案映射 (proposal_id -> GovernanceProposal)
|
||
pub proposals: HashMap<String, GovernanceProposal>,
|
||
/// 资产提案列表 (asset_id -> [proposal_ids])
|
||
pub asset_proposals: HashMap<String, Vec<String>>,
|
||
/// 提案人提案列表 (proposer -> [proposal_ids])
|
||
pub proposer_proposals: HashMap<String, Vec<String>>,
|
||
/// 投票记录 (vote_id -> VoteRecord)
|
||
pub vote_records: HashMap<String, VoteRecord>,
|
||
}
|
||
|
||
/// ACC-Governance接口
|
||
pub trait ACCGovernance {
|
||
/// 创建提案
|
||
fn create_proposal(&mut self, proposal: GovernanceProposal) -> Result<(), GovernanceError>;
|
||
|
||
/// 获取提案
|
||
fn get_proposal(&self, proposal_id: &str) -> Result<GovernanceProposal, GovernanceError>;
|
||
|
||
/// 投票
|
||
fn vote(
|
||
&mut self,
|
||
proposal_id: &str,
|
||
voter: &str,
|
||
option: VoteOption,
|
||
voting_power: u128,
|
||
) -> Result<(), GovernanceError>;
|
||
|
||
/// 执行提案
|
||
fn execute_proposal(&mut self, proposal_id: &str) -> Result<(), GovernanceError>;
|
||
|
||
/// 取消提案
|
||
fn cancel_proposal(&mut self, proposal_id: &str) -> Result<(), GovernanceError>;
|
||
|
||
/// 获取提案结果
|
||
fn get_proposal_result(&self, proposal_id: &str) -> Result<(u128, u128, u128), GovernanceError>;
|
||
|
||
/// 检查提案是否通过
|
||
fn is_proposal_passed(&self, proposal_id: &str) -> bool;
|
||
|
||
/// 获取资产的提案列表
|
||
fn get_asset_proposals(&self, asset_id: &str) -> Vec<GovernanceProposal>;
|
||
|
||
/// 获取提案人的提案列表
|
||
fn get_proposer_proposals(&self, proposer: &str) -> Vec<GovernanceProposal>;
|
||
|
||
/// 获取投票记录
|
||
fn get_vote_record(&self, vote_id: &str) -> Result<VoteRecord, GovernanceError>;
|
||
|
||
/// 获取活跃提案列表
|
||
fn get_active_proposals(&self) -> Vec<GovernanceProposal>;
|
||
|
||
/// 更新提案状态
|
||
fn update_proposal_status(
|
||
&mut self,
|
||
proposal_id: &str,
|
||
status: ProposalStatus,
|
||
) -> Result<(), GovernanceError>;
|
||
}
|
||
|
||
/// ACC-Governance标准实现
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct GovernanceToken {
|
||
state: GovernanceState,
|
||
}
|
||
|
||
impl GovernanceToken {
|
||
/// 创建新的治理管理器
|
||
pub fn new() -> Self {
|
||
Self {
|
||
state: GovernanceState {
|
||
proposals: HashMap::new(),
|
||
asset_proposals: HashMap::new(),
|
||
proposer_proposals: HashMap::new(),
|
||
vote_records: HashMap::new(),
|
||
},
|
||
}
|
||
}
|
||
|
||
/// 获取状态的可变引用
|
||
pub fn state_mut(&mut self) -> &mut GovernanceState {
|
||
&mut self.state
|
||
}
|
||
|
||
/// 获取状态的不可变引用
|
||
pub fn state(&self) -> &GovernanceState {
|
||
&self.state
|
||
}
|
||
|
||
/// 检查投票是否有效
|
||
fn is_voting_active(&self, proposal: &GovernanceProposal) -> bool {
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
now >= proposal.voting_start && now <= proposal.voting_end
|
||
}
|
||
}
|
||
|
||
impl Default for GovernanceToken {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl ACCGovernance for GovernanceToken {
|
||
fn create_proposal(&mut self, mut proposal: GovernanceProposal) -> Result<(), GovernanceError> {
|
||
// 检查提案是否已存在
|
||
if self.state.proposals.contains_key(&proposal.proposal_id) {
|
||
return Err(GovernanceError::ProposalAlreadyExists);
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
proposal.created_at = now;
|
||
proposal.status = ProposalStatus::Active;
|
||
proposal.votes_for = 0;
|
||
proposal.votes_against = 0;
|
||
proposal.votes_abstain = 0;
|
||
|
||
let proposal_id = proposal.proposal_id.clone();
|
||
let asset_id = proposal.asset_id.clone();
|
||
let proposer = proposal.proposer.clone();
|
||
|
||
// 保存提案
|
||
self.state
|
||
.proposals
|
||
.insert(proposal_id.clone(), proposal);
|
||
|
||
// 更新资产提案列表
|
||
self.state
|
||
.asset_proposals
|
||
.entry(asset_id)
|
||
.or_insert_with(Vec::new)
|
||
.push(proposal_id.clone());
|
||
|
||
// 更新提案人提案列表
|
||
self.state
|
||
.proposer_proposals
|
||
.entry(proposer)
|
||
.or_insert_with(Vec::new)
|
||
.push(proposal_id);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn get_proposal(&self, proposal_id: &str) -> Result<GovernanceProposal, GovernanceError> {
|
||
self.state
|
||
.proposals
|
||
.get(proposal_id)
|
||
.cloned()
|
||
.ok_or(GovernanceError::ProposalNotFound)
|
||
}
|
||
|
||
fn vote(
|
||
&mut self,
|
||
proposal_id: &str,
|
||
voter: &str,
|
||
option: VoteOption,
|
||
voting_power: u128,
|
||
) -> Result<(), GovernanceError> {
|
||
let mut proposal = self.get_proposal(proposal_id)?;
|
||
|
||
// 检查投票是否有效
|
||
if !self.is_voting_active(&proposal) {
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
if now < proposal.voting_start {
|
||
return Err(GovernanceError::VotingNotStarted);
|
||
} else {
|
||
return Err(GovernanceError::VotingEnded);
|
||
}
|
||
}
|
||
|
||
// 检查是否已投票
|
||
if proposal.votes.contains_key(voter) {
|
||
return Err(GovernanceError::AlreadyVoted);
|
||
}
|
||
|
||
// 检查投票权重
|
||
if voting_power == 0 {
|
||
return Err(GovernanceError::InvalidVotingPower);
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
// 更新投票统计
|
||
match option {
|
||
VoteOption::For => proposal.votes_for += voting_power,
|
||
VoteOption::Against => proposal.votes_against += voting_power,
|
||
VoteOption::Abstain => proposal.votes_abstain += voting_power,
|
||
}
|
||
|
||
// 记录投票
|
||
proposal.votes.insert(voter.to_string(), option.clone());
|
||
|
||
// 保存提案
|
||
self.state
|
||
.proposals
|
||
.insert(proposal_id.to_string(), proposal);
|
||
|
||
// 创建投票记录
|
||
let vote_id = format!("{}_{}", proposal_id, voter);
|
||
let vote_record = VoteRecord {
|
||
vote_id: vote_id.clone(),
|
||
proposal_id: proposal_id.to_string(),
|
||
voter: voter.to_string(),
|
||
option,
|
||
voting_power,
|
||
voted_at: now,
|
||
};
|
||
|
||
self.state.vote_records.insert(vote_id, vote_record);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn execute_proposal(&mut self, proposal_id: &str) -> Result<(), GovernanceError> {
|
||
let mut proposal = self.get_proposal(proposal_id)?;
|
||
|
||
// 检查提案是否通过
|
||
if !self.is_proposal_passed(proposal_id) {
|
||
return Err(GovernanceError::ProposalNotPassed);
|
||
}
|
||
|
||
// 检查提案是否已执行
|
||
if proposal.status == ProposalStatus::Executed {
|
||
return Err(GovernanceError::ProposalAlreadyExecuted);
|
||
}
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
proposal.status = ProposalStatus::Executed;
|
||
proposal.executed_at = Some(now);
|
||
|
||
self.state
|
||
.proposals
|
||
.insert(proposal_id.to_string(), proposal);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn cancel_proposal(&mut self, proposal_id: &str) -> Result<(), GovernanceError> {
|
||
let mut proposal = self.get_proposal(proposal_id)?;
|
||
|
||
if proposal.status == ProposalStatus::Executed {
|
||
return Err(GovernanceError::ProposalAlreadyExecuted);
|
||
}
|
||
|
||
proposal.status = ProposalStatus::Cancelled;
|
||
|
||
self.state
|
||
.proposals
|
||
.insert(proposal_id.to_string(), proposal);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
fn get_proposal_result(&self, proposal_id: &str) -> Result<(u128, u128, u128), GovernanceError> {
|
||
let proposal = self.get_proposal(proposal_id)?;
|
||
Ok((
|
||
proposal.votes_for,
|
||
proposal.votes_against,
|
||
proposal.votes_abstain,
|
||
))
|
||
}
|
||
|
||
fn is_proposal_passed(&self, proposal_id: &str) -> bool {
|
||
if let Ok(proposal) = self.get_proposal(proposal_id) {
|
||
let total_votes = proposal.votes_for + proposal.votes_against + proposal.votes_abstain;
|
||
if total_votes == 0 {
|
||
return false;
|
||
}
|
||
|
||
let approval_rate = (proposal.votes_for * 10000 / total_votes) as u16;
|
||
approval_rate >= proposal.quorum_threshold
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
fn get_asset_proposals(&self, asset_id: &str) -> Vec<GovernanceProposal> {
|
||
self.state
|
||
.asset_proposals
|
||
.get(asset_id)
|
||
.map(|proposal_ids| {
|
||
proposal_ids
|
||
.iter()
|
||
.filter_map(|id| self.state.proposals.get(id).cloned())
|
||
.collect()
|
||
})
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
fn get_proposer_proposals(&self, proposer: &str) -> Vec<GovernanceProposal> {
|
||
self.state
|
||
.proposer_proposals
|
||
.get(proposer)
|
||
.map(|proposal_ids| {
|
||
proposal_ids
|
||
.iter()
|
||
.filter_map(|id| self.state.proposals.get(id).cloned())
|
||
.collect()
|
||
})
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
fn get_vote_record(&self, vote_id: &str) -> Result<VoteRecord, GovernanceError> {
|
||
self.state
|
||
.vote_records
|
||
.get(vote_id)
|
||
.cloned()
|
||
.ok_or(GovernanceError::ProposalNotFound)
|
||
}
|
||
|
||
fn get_active_proposals(&self) -> Vec<GovernanceProposal> {
|
||
self.state
|
||
.proposals
|
||
.values()
|
||
.filter(|proposal| proposal.status == ProposalStatus::Active)
|
||
.cloned()
|
||
.collect()
|
||
}
|
||
|
||
fn update_proposal_status(
|
||
&mut self,
|
||
proposal_id: &str,
|
||
status: ProposalStatus,
|
||
) -> Result<(), GovernanceError> {
|
||
let mut proposal = self.get_proposal(proposal_id)?;
|
||
proposal.status = status;
|
||
self.state
|
||
.proposals
|
||
.insert(proposal_id.to_string(), proposal);
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn create_test_proposal() -> GovernanceProposal {
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
GovernanceProposal {
|
||
proposal_id: "PROP-001".to_string(),
|
||
asset_id: "RWA-001".to_string(),
|
||
proposer: "alice".to_string(),
|
||
proposal_type: ProposalType::ParameterChange,
|
||
title: "Test Proposal".to_string(),
|
||
description: "This is a test proposal".to_string(),
|
||
status: ProposalStatus::Active,
|
||
created_at: now,
|
||
voting_start: now - 3600, // 1小时前开始
|
||
voting_end: now + 86400 * 7, // 7天后结束
|
||
executed_at: None,
|
||
votes_for: 0,
|
||
votes_against: 0,
|
||
votes_abstain: 0,
|
||
quorum_threshold: 5000, // 50%
|
||
votes: HashMap::new(),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_create_proposal() {
|
||
let mut governance = GovernanceToken::new();
|
||
let proposal = create_test_proposal();
|
||
assert!(governance.create_proposal(proposal).is_ok());
|
||
}
|
||
|
||
#[test]
|
||
fn test_vote() {
|
||
let mut governance = GovernanceToken::new();
|
||
let proposal = create_test_proposal();
|
||
governance.create_proposal(proposal).unwrap();
|
||
|
||
assert!(governance
|
||
.vote("PROP-001", "bob", VoteOption::For, 1000)
|
||
.is_ok());
|
||
|
||
let (votes_for, _, _) = governance.get_proposal_result("PROP-001").unwrap();
|
||
assert_eq!(votes_for, 1000);
|
||
}
|
||
|
||
#[test]
|
||
fn test_is_proposal_passed() {
|
||
let mut governance = GovernanceToken::new();
|
||
let proposal = create_test_proposal();
|
||
governance.create_proposal(proposal).unwrap();
|
||
|
||
governance
|
||
.vote("PROP-001", "bob", VoteOption::For, 6000)
|
||
.unwrap();
|
||
governance
|
||
.vote("PROP-001", "charlie", VoteOption::Against, 4000)
|
||
.unwrap();
|
||
|
||
assert!(governance.is_proposal_passed("PROP-001"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_execute_proposal() {
|
||
let mut governance = GovernanceToken::new();
|
||
let proposal = create_test_proposal();
|
||
governance.create_proposal(proposal).unwrap();
|
||
|
||
governance
|
||
.vote("PROP-001", "bob", VoteOption::For, 6000)
|
||
.unwrap();
|
||
governance
|
||
.vote("PROP-001", "charlie", VoteOption::Against, 4000)
|
||
.unwrap();
|
||
|
||
assert!(governance.execute_proposal("PROP-001").is_ok());
|
||
assert_eq!(
|
||
governance.get_proposal("PROP-001").unwrap().status,
|
||
ProposalStatus::Executed
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_cancel_proposal() {
|
||
let mut governance = GovernanceToken::new();
|
||
let proposal = create_test_proposal();
|
||
governance.create_proposal(proposal).unwrap();
|
||
|
||
assert!(governance.cancel_proposal("PROP-001").is_ok());
|
||
assert_eq!(
|
||
governance.get_proposal("PROP-001").unwrap().status,
|
||
ProposalStatus::Cancelled
|
||
);
|
||
}
|
||
}
|