anya_core/dao/
voting.rs

1use serde::{Deserialize, Serialize};
2/// Quadratic Voting Implementation [AIS-3][BPC-3][DAO-3]
3// [AIR-3][AIS-3][BPC-3][RES-3] Removed unused import: std::error::Error
4use std::collections::HashMap;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum VotingError {
9    #[error("Invalid vote: {0}")]
10    InvalidVote(String),
11
12    #[error("Voting not allowed: {0}")]
13    VotingNotAllowed(String),
14
15    #[error("Quadratic calculation error: {0}")]
16    QuadraticError(String),
17}
18
19/// Vote on a proposal
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct Vote {
22    /// Proposal being voted on
23    pub proposal_id: String,
24
25    /// Voter identifier
26    pub voter: String,
27
28    /// Vote power (squared for quadratic voting)
29    pub power: u64,
30
31    /// Direction of vote
32    pub direction: VoteDirection,
33
34    /// Bitcoin signature (BPC-3 compliance)
35    pub bitcoin_signature: Option<String>,
36}
37
38/// Direction of a vote
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40pub enum VoteDirection {
41    /// Vote in favor
42    For,
43
44    /// Vote against
45    Against,
46
47    /// Abstain from voting
48    Abstain,
49}
50
51/// Quadratic voting system for DAO governance
52pub struct QuadraticVoting {
53    /// Votes by proposal ID and voter
54    votes: HashMap<String, HashMap<String, Vote>>,
55}
56
57impl Default for QuadraticVoting {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl QuadraticVoting {
64    /// Create a new quadratic voting system
65    pub fn new() -> Self {
66        Self {
67            votes: HashMap::new(),
68        }
69    }
70
71    /// Cast a vote using quadratic voting rules
72    pub fn cast_vote(&mut self, vote: Vote) -> Result<(), VotingError> {
73        // Validate vote
74        if vote.power == 0 {
75            return Err(VotingError::InvalidVote(
76                "Vote power cannot be zero".to_string(),
77            ));
78        }
79
80        // For DAO-4, require Bitcoin signature
81        if vote.bitcoin_signature.is_none() {
82            return Err(VotingError::InvalidVote(
83                "Bitcoin signature required for BPC-3 compliance".to_string(),
84            ));
85        }
86
87        // Get or create proposal votes
88        let proposal_votes = self.votes.entry(vote.proposal_id.clone()).or_default();
89
90        // Store the vote
91        proposal_votes.insert(vote.voter.clone(), vote);
92
93        Ok(())
94    }
95
96    /// Calculate quadratic voting results for a proposal
97    pub fn tally_votes(&self, proposal_id: &str) -> Result<VoteTally, VotingError> {
98        let proposal_votes = self.votes.get(proposal_id).ok_or_else(|| {
99            VotingError::InvalidVote(format!("No votes found for proposal {proposal_id}"))
100        })?;
101
102        let mut for_votes = 0.0;
103        let mut against_votes = 0.0;
104        let mut abstain_votes = 0.0;
105
106        for vote in proposal_votes.values() {
107            // Calculate quadratic vote power (square root of power)
108            let quadratic_power = (vote.power as f64).sqrt();
109
110            match vote.direction {
111                VoteDirection::For => for_votes += quadratic_power,
112                VoteDirection::Against => against_votes += quadratic_power,
113                VoteDirection::Abstain => abstain_votes += quadratic_power,
114            }
115        }
116
117        Ok(VoteTally {
118            proposal_id: proposal_id.to_string(),
119            for_votes,
120            against_votes,
121            abstain_votes,
122            total_votes: proposal_votes.len(),
123        })
124    }
125}
126
127/// Results of a vote tally
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct VoteTally {
130    /// Proposal that was voted on
131    pub proposal_id: String,
132
133    /// Quadratic votes in favor
134    pub for_votes: f64,
135
136    /// Quadratic votes against
137    pub against_votes: f64,
138
139    /// Quadratic abstentions
140    pub abstain_votes: f64,
141
142    /// Total number of voters
143    pub total_votes: usize,
144}