ruvector_dag/qudag/tokens/
governance.rs1use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
6pub struct Proposal {
7 pub id: String,
8 pub title: String,
9 pub description: String,
10 pub proposer: String,
11 pub created_at: std::time::Instant,
12 pub voting_ends: std::time::Duration,
13 pub proposal_type: ProposalType,
14 pub status: ProposalStatus,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum ProposalType {
19 ParameterChange,
20 PatternPolicy,
21 RewardAdjustment,
22 ProtocolUpgrade,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub enum ProposalStatus {
27 Active,
28 Passed,
29 Failed,
30 Executed,
31 Cancelled,
32}
33
34#[derive(Debug, Clone)]
35pub struct GovernanceVote {
36 pub voter: String,
37 pub proposal_id: String,
38 pub vote: VoteChoice,
39 pub weight: f64,
40 pub timestamp: std::time::Instant,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub enum VoteChoice {
45 For,
46 Against,
47 Abstain,
48}
49
50pub struct GovernanceSystem {
51 proposals: HashMap<String, Proposal>,
52 votes: HashMap<String, Vec<GovernanceVote>>,
53 quorum_threshold: f64, approval_threshold: f64, }
56
57impl GovernanceSystem {
58 pub fn new(quorum_threshold: f64, approval_threshold: f64) -> Self {
59 Self {
60 proposals: HashMap::new(),
61 votes: HashMap::new(),
62 quorum_threshold,
63 approval_threshold,
64 }
65 }
66
67 pub fn create_proposal(
68 &mut self,
69 title: String,
70 description: String,
71 proposer: String,
72 proposal_type: ProposalType,
73 voting_duration: std::time::Duration,
74 ) -> String {
75 let id = format!("prop_{}", rand::random::<u64>());
76
77 let proposal = Proposal {
78 id: id.clone(),
79 title,
80 description,
81 proposer,
82 created_at: std::time::Instant::now(),
83 voting_ends: voting_duration,
84 proposal_type,
85 status: ProposalStatus::Active,
86 };
87
88 self.proposals.insert(id.clone(), proposal);
89 self.votes.insert(id.clone(), Vec::new());
90
91 id
92 }
93
94 pub fn vote(
95 &mut self,
96 voter: String,
97 proposal_id: &str,
98 choice: VoteChoice,
99 stake_weight: f64,
100 ) -> Result<(), GovernanceError> {
101 let proposal = self
102 .proposals
103 .get(proposal_id)
104 .ok_or(GovernanceError::ProposalNotFound)?;
105
106 if proposal.status != ProposalStatus::Active {
107 return Err(GovernanceError::ProposalNotActive);
108 }
109
110 if proposal.created_at.elapsed() > proposal.voting_ends {
111 return Err(GovernanceError::VotingEnded);
112 }
113
114 let votes = self.votes.get_mut(proposal_id).unwrap();
116 if votes.iter().any(|v| v.voter == voter) {
117 return Err(GovernanceError::AlreadyVoted);
118 }
119
120 votes.push(GovernanceVote {
121 voter,
122 proposal_id: proposal_id.to_string(),
123 vote: choice,
124 weight: stake_weight,
125 timestamp: std::time::Instant::now(),
126 });
127
128 Ok(())
129 }
130
131 pub fn tally(&self, proposal_id: &str, total_stake: f64) -> Option<VoteTally> {
132 let votes = self.votes.get(proposal_id)?;
133
134 let mut for_weight = 0.0;
135 let mut against_weight = 0.0;
136 let mut abstain_weight = 0.0;
137
138 for vote in votes {
139 match vote.vote {
140 VoteChoice::For => for_weight += vote.weight,
141 VoteChoice::Against => against_weight += vote.weight,
142 VoteChoice::Abstain => abstain_weight += vote.weight,
143 }
144 }
145
146 let total_voted = for_weight + against_weight + abstain_weight;
147 let participation = total_voted / total_stake;
148 let approval = if for_weight + against_weight > 0.0 {
149 for_weight / (for_weight + against_weight)
150 } else {
151 0.0
152 };
153
154 let quorum_met = participation >= self.quorum_threshold;
155 let approved = approval >= self.approval_threshold && quorum_met;
156
157 Some(VoteTally {
158 for_weight,
159 against_weight,
160 abstain_weight,
161 participation,
162 approval,
163 quorum_met,
164 approved,
165 })
166 }
167
168 pub fn finalize(
169 &mut self,
170 proposal_id: &str,
171 total_stake: f64,
172 ) -> Result<ProposalStatus, GovernanceError> {
173 {
175 let proposal = self
176 .proposals
177 .get(proposal_id)
178 .ok_or(GovernanceError::ProposalNotFound)?;
179
180 if proposal.status != ProposalStatus::Active {
181 return Err(GovernanceError::ProposalNotActive);
182 }
183
184 if proposal.created_at.elapsed() < proposal.voting_ends {
185 return Err(GovernanceError::VotingNotEnded);
186 }
187 }
188
189 let tally = self
191 .tally(proposal_id, total_stake)
192 .ok_or(GovernanceError::ProposalNotFound)?;
193
194 let new_status = if tally.approved {
195 ProposalStatus::Passed
196 } else {
197 ProposalStatus::Failed
198 };
199
200 let proposal = self.proposals.get_mut(proposal_id).unwrap();
202 proposal.status = new_status;
203 Ok(new_status)
204 }
205
206 pub fn get_proposal(&self, proposal_id: &str) -> Option<&Proposal> {
207 self.proposals.get(proposal_id)
208 }
209
210 pub fn active_proposals(&self) -> Vec<&Proposal> {
211 self.proposals
212 .values()
213 .filter(|p| p.status == ProposalStatus::Active)
214 .collect()
215 }
216}
217
218#[derive(Debug, Clone)]
219pub struct VoteTally {
220 pub for_weight: f64,
221 pub against_weight: f64,
222 pub abstain_weight: f64,
223 pub participation: f64,
224 pub approval: f64,
225 pub quorum_met: bool,
226 pub approved: bool,
227}
228
229#[derive(Debug, thiserror::Error)]
230pub enum GovernanceError {
231 #[error("Proposal not found")]
232 ProposalNotFound,
233 #[error("Proposal not active")]
234 ProposalNotActive,
235 #[error("Voting has ended")]
236 VotingEnded,
237 #[error("Voting has not ended")]
238 VotingNotEnded,
239 #[error("Already voted")]
240 AlreadyVoted,
241 #[error("Insufficient stake to propose")]
242 InsufficientStake,
243}
244
245impl Default for GovernanceSystem {
246 fn default() -> Self {
247 Self::new(0.1, 0.67) }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use std::time::Duration;
255
256 #[test]
257 fn test_proposal_creation() {
258 let mut gov = GovernanceSystem::default();
259 let id = gov.create_proposal(
260 "Test".to_string(),
261 "Description".to_string(),
262 "proposer1".to_string(),
263 ProposalType::ParameterChange,
264 Duration::from_secs(86400),
265 );
266
267 let proposal = gov.get_proposal(&id).unwrap();
268 assert_eq!(proposal.title, "Test");
269 assert_eq!(proposal.status, ProposalStatus::Active);
270 }
271
272 #[test]
273 fn test_voting() {
274 let mut gov = GovernanceSystem::default();
275 let id = gov.create_proposal(
276 "Test".to_string(),
277 "Description".to_string(),
278 "proposer1".to_string(),
279 ProposalType::ParameterChange,
280 Duration::from_secs(86400),
281 );
282
283 assert!(gov
285 .vote("voter1".to_string(), &id, VoteChoice::For, 100.0)
286 .is_ok());
287
288 assert!(matches!(
290 gov.vote("voter1".to_string(), &id, VoteChoice::For, 50.0),
291 Err(GovernanceError::AlreadyVoted)
292 ));
293 }
294
295 #[test]
296 fn test_tally() {
297 let mut gov = GovernanceSystem::new(0.1, 0.5);
298 let id = gov.create_proposal(
299 "Test".to_string(),
300 "Description".to_string(),
301 "proposer1".to_string(),
302 ProposalType::ParameterChange,
303 Duration::from_secs(86400),
304 );
305
306 gov.vote("voter1".to_string(), &id, VoteChoice::For, 700.0)
307 .unwrap();
308 gov.vote("voter2".to_string(), &id, VoteChoice::Against, 300.0)
309 .unwrap();
310
311 let tally = gov.tally(&id, 10000.0).unwrap();
312 assert_eq!(tally.for_weight, 700.0);
313 assert_eq!(tally.against_weight, 300.0);
314 assert_eq!(tally.participation, 0.1); assert_eq!(tally.approval, 0.7); assert!(tally.quorum_met);
317 assert!(tally.approved);
318 }
319
320 #[test]
321 fn test_quorum_not_met() {
322 let mut gov = GovernanceSystem::new(0.5, 0.67);
323 let id = gov.create_proposal(
324 "Test".to_string(),
325 "Description".to_string(),
326 "proposer1".to_string(),
327 ProposalType::ParameterChange,
328 Duration::from_secs(86400),
329 );
330
331 gov.vote("voter1".to_string(), &id, VoteChoice::For, 100.0)
332 .unwrap();
333
334 let tally = gov.tally(&id, 10000.0).unwrap();
335 assert!(!tally.quorum_met); assert!(!tally.approved);
337 }
338}