use crate::AnyaError;
use chrono::Utc;
use rand::random;
use serde_json::json as serde_json;
use std::collections::HashMap;
use std::error::Error;
pub mod compat;
pub mod governance;
pub mod legal;
pub mod voting;
pub use governance::DaoLevel;
#[cfg(test)]
mod tests;
pub use governance::{DaoGovernance, ProposalStatus};
#[derive(Debug, Clone)]
pub struct Proposal {
pub id: String,
pub title: String,
pub description: String,
pub author: String,
pub status: ProposalStatus,
pub votes: i32,
pub votes_for: u64,
pub votes_against: u64,
pub amount: u64,
pub proposer: String,
pub created_at: chrono::DateTime<Utc>,
pub updated_at: chrono::DateTime<Utc>,
pub execution_time: Option<chrono::DateTime<Utc>>,
}
#[derive(Debug, Clone, Default)]
pub struct ProposalMetrics {
pub proposal_count: usize,
pub passed_count: usize,
pub rejected_count: usize,
pub active_count: usize,
pub sentiment_score: f64,
pub risk_assessment: RiskMetrics,
pub ml_predictions: std::collections::HashMap<String, f64>,
pub federated_consensus: std::collections::HashMap<String, f64>,
pub last_updated: chrono::DateTime<Utc>,
}
#[derive(Debug, Clone, Default)]
pub struct RiskMetrics {
pub risk_score: f64,
pub compliance_level: String,
pub audit_status: bool,
pub risk_factors: Vec<(String, f64)>,
pub mitigation_suggestions: Vec<String>,
pub last_updated: chrono::DateTime<Utc>,
}
#[derive(Debug, Clone)]
pub struct DAOConfig {
pub enabled: bool,
pub contract_address: Option<String>,
pub proposal_threshold: u64,
pub voting_period_blocks: u32,
pub time_lock_blocks: u32,
}
impl Default for DAOConfig {
fn default() -> Self {
Self {
enabled: true,
contract_address: None,
proposal_threshold: 100_000_000, voting_period_blocks: 1008, time_lock_blocks: 144, }
}
}
pub struct DAOManager {
config: DAOConfig,
proposals: HashMap<String, Proposal>,
}
impl DAOManager {
pub fn new(config: DAOConfig) -> Result<Self, Box<dyn Error>> {
if !config.enabled {
return Ok(Self {
config,
proposals: HashMap::new(),
});
}
Ok(Self {
config,
proposals: HashMap::new(),
})
}
pub fn create_proposal(
&mut self,
title: &str,
description: &str,
amount: u64,
) -> Result<Proposal, Box<dyn Error>> {
if amount < self.config.proposal_threshold {
return Err(Box::new(AnyaError::DAO(format!(
"Proposal amount ({}) is below the threshold ({})",
amount, self.config.proposal_threshold
))));
}
let proposal_id = format!("proposal:{:x}", random::<u64>());
let proposal = Proposal {
id: proposal_id.clone(),
title: title.to_string(),
description: description.to_string(),
author: "unknown".to_string(),
proposer: "unknown".to_string(), amount,
votes_for: 0,
votes_against: 0,
votes: 0,
status: ProposalStatus::Active,
created_at: Utc::now(),
updated_at: Utc::now(),
execution_time: None,
};
self.proposals.insert(proposal_id.clone(), proposal.clone());
Ok(proposal)
}
pub fn vote(
&mut self,
proposal_id: &str,
vote_for: bool,
amount: u64,
) -> Result<(), Box<dyn Error>> {
let proposal = self.proposals.get_mut(proposal_id).ok_or_else(|| {
Box::new(AnyaError::DAO(format!("Proposal not found: {proposal_id}")))
})?;
if proposal.status != ProposalStatus::Active {
return Err(Box::new(AnyaError::DAO(format!(
"Proposal is not active: {:?}",
proposal.status
))));
}
if vote_for {
proposal.votes_for += amount;
} else {
proposal.votes_against += amount;
}
proposal.updated_at = Utc::now();
Ok(())
}
pub fn get_proposal(&self, proposal_id: &str) -> Result<Proposal, Box<dyn Error>> {
Ok(self.proposals.get(proposal_id).cloned().ok_or_else(|| {
Box::new(AnyaError::DAO(format!("Proposal not found: {proposal_id}")))
})?)
}
pub fn list_proposals(&self) -> Vec<Proposal> {
self.proposals.values().cloned().collect()
}
pub fn execute_proposal(&mut self, proposal_id: &str) -> Result<(), Box<dyn Error>> {
let proposal = self.proposals.get_mut(proposal_id).ok_or_else(|| {
Box::new(AnyaError::DAO(format!("Proposal not found: {proposal_id}")))
})?;
if proposal.status != ProposalStatus::Active {
return Err(Box::new(AnyaError::DAO(format!(
"Proposal is not active: {:?}",
proposal.status
))));
}
if proposal.votes_for <= proposal.votes_against {
return Err(Box::new(AnyaError::DAO(format!(
"Proposal does not have enough votes: {} vs {}",
proposal.votes_for, proposal.votes_against
))));
}
proposal.status = ProposalStatus::Executed;
proposal.execution_time = Some(Utc::now());
proposal.updated_at = Utc::now();
Ok(())
}
pub fn get_status(&self) -> (bool, u8) {
let operational = self.config.enabled;
let health = if operational {
if self.config.contract_address.is_some() {
100
} else {
70 }
} else {
0
};
(operational, health)
}
pub fn get_metrics(&self) -> HashMap<String, serde_json::Value> {
let mut metrics = HashMap::new();
metrics.insert(
"enabled".to_string(),
serde_json::json!(self.config.enabled),
);
metrics.insert(
"proposal_count".to_string(),
serde_json::json!(self.proposals.len()),
);
metrics.insert(
"proposal_threshold".to_string(),
serde_json::json!(self.config.proposal_threshold),
);
metrics.insert(
"voting_period_blocks".to_string(),
serde_json::json!(self.config.voting_period_blocks),
);
if let Some(address) = &self.config.contract_address {
metrics.insert("contract_address".to_string(), serde_json::json!(address));
}
let active_proposals = self
.proposals
.values()
.filter(|p| p.status == ProposalStatus::Active)
.count();
let passed_proposals = self
.proposals
.values()
.filter(|p| p.status == ProposalStatus::Passed)
.count();
let executed_proposals = self
.proposals
.values()
.filter(|p| p.status == ProposalStatus::Executed)
.count();
metrics.insert(
"active_proposals".to_string(),
serde_json::json!(active_proposals),
);
metrics.insert(
"passed_proposals".to_string(),
serde_json!(passed_proposals),
);
metrics.insert(
"executed_proposals".to_string(),
serde_json!(executed_proposals),
);
metrics
}
}