use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum VotingError {
#[error("Invalid vote: {0}")]
InvalidVote(String),
#[error("Voting not allowed: {0}")]
VotingNotAllowed(String),
#[error("Quadratic calculation error: {0}")]
QuadraticError(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vote {
pub proposal_id: String,
pub voter: String,
pub power: u64,
pub direction: VoteDirection,
pub bitcoin_signature: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum VoteDirection {
For,
Against,
Abstain,
}
pub struct QuadraticVoting {
votes: HashMap<String, HashMap<String, Vote>>,
}
impl Default for QuadraticVoting {
fn default() -> Self {
Self::new()
}
}
impl QuadraticVoting {
pub fn new() -> Self {
Self {
votes: HashMap::new(),
}
}
pub fn cast_vote(&mut self, vote: Vote) -> Result<(), VotingError> {
if vote.power == 0 {
return Err(VotingError::InvalidVote(
"Vote power cannot be zero".to_string(),
));
}
if vote.bitcoin_signature.is_none() {
return Err(VotingError::InvalidVote(
"Bitcoin signature required for BPC-3 compliance".to_string(),
));
}
let proposal_votes = self.votes.entry(vote.proposal_id.clone()).or_default();
proposal_votes.insert(vote.voter.clone(), vote);
Ok(())
}
pub fn tally_votes(&self, proposal_id: &str) -> Result<VoteTally, VotingError> {
let proposal_votes = self.votes.get(proposal_id).ok_or_else(|| {
VotingError::InvalidVote(format!("No votes found for proposal {proposal_id}"))
})?;
let mut for_votes = 0.0;
let mut against_votes = 0.0;
let mut abstain_votes = 0.0;
for vote in proposal_votes.values() {
let quadratic_power = (vote.power as f64).sqrt();
match vote.direction {
VoteDirection::For => for_votes += quadratic_power,
VoteDirection::Against => against_votes += quadratic_power,
VoteDirection::Abstain => abstain_votes += quadratic_power,
}
}
Ok(VoteTally {
proposal_id: proposal_id.to_string(),
for_votes,
against_votes,
abstain_votes,
total_votes: proposal_votes.len(),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoteTally {
pub proposal_id: String,
pub for_votes: f64,
pub against_votes: f64,
pub abstain_votes: f64,
pub total_votes: usize,
}