Skip to content

Commit 66e19ad

Browse files
authored
Merge pull request #107 from GoSTEAN/dao
fac: Refactor and fix the DAO governance smart contract closes #90
2 parents 4fbaea3 + e36b8de commit 66e19ad

8 files changed

Lines changed: 1756 additions & 276 deletions

File tree

contracts/Cargo.lock

Lines changed: 1119 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contracts/dao-governance/Cargo.toml

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ version = "0.1.0"
44
edition = "2021"
55

66
[dependencies]
7-
ink = { version = "4.2.1", default-features = false }
8-
ink_lang = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_lang" }
9-
ink_storage = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_storage" }
10-
ink_env = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_env" }
11-
ink_prelude = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_prelude" }
12-
ink_primitives = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_primitives" }
7+
ink = { version = "4.2.0", default-features = false }
8+
ink_metadata = { version = "4.2.0", default-features = false }
9+
ink_storage = { version = "4.2.0", default-features = false }
10+
ink_prelude = { version = "4.2.0", default-features = false }
11+
scale = { package = "parity-scale-codec", version = "3.6.5", default-features = false, features = ["derive"] }
12+
scale-info = { version = "2.9.0", default-features = false, features = ["derive"] }
13+
serde = { version = "1.0", default-features = false, features = ["derive"] }
14+
thiserror = "1.0"
1315

14-
parity-scale-codec = { version = "3.6", default-features = false }
15-
scale-info = { version = "2.10", features = ["derive"], optional = true }
16-
serde = { version = "1.0", features = ["derive"], optional = true }
17-
thiserror = { version = "1.0", optional = true }
16+
[dev-dependencies]
17+
ink_env = { version = "4.2.0", default-features = false }
1818

1919
[lib]
2020
name = "dao_governance"
@@ -24,13 +24,13 @@ crate-type = ["cdylib", "rlib"]
2424
[features]
2525
default = ["std"]
2626
std = [
27-
"ink/std",
28-
"ink_lang/std",
29-
"ink_storage/std",
30-
"parity-scale-codec/std",
31-
"scale-info/std",
32-
"serde",
33-
"thiserror",
27+
"ink/std",
28+
"ink_metadata/std",
29+
"ink_storage/std",
30+
"ink_prelude/std",
31+
"scale/std",
32+
"scale-info/std",
33+
"serde/std",
3434
]
3535

3636
# For testing with cargo-contract

contracts/dao-governance/src/dao_tests.rs

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 111 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use serde::{Deserialize, Serialize};
22
use thiserror::Error;
3-
3+
use std::collections::HashMap;
44

55
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
66
pub enum Role {
@@ -9,102 +9,164 @@ pub enum Role {
99
Admin,
1010
}
1111

12-
1312
#[derive(Debug, Clone, Serialize, Deserialize)]
1413
pub struct UserIdentity {
15-
pub address: String,
14+
pub address: String,
1615
pub role: Role,
1716
}
1817

19-
20-
#[cfg_attr(feature = "std", derive(thiserror::Error))]
21-
pub enum GovernanceError {
22-
#[error("You are not authorized to perform this action.")]
23-
Unauthorized,
24-
25-
#[error("The requested proposal was not found.")]
26-
ProposalNotFound,
27-
28-
#[error("Invalid operation.")]
29-
InvalidOperation,
18+
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
19+
pub enum ProposalStatus {
20+
Active,
21+
Accepted,
22+
Rejected,
23+
Expired,
3024
}
3125

32-
3326
#[derive(Debug, Clone, Serialize, Deserialize)]
3427
pub struct Proposal {
3528
pub id: u64,
3629
pub title: String,
3730
pub description: String,
38-
pub proposer: String,
31+
pub proposer: String,
3932
pub votes_for: u32,
4033
pub votes_against: u32,
34+
pub status: ProposalStatus,
35+
pub end_time: u64, // Unix timestamp for voting deadline
4136
}
4237

38+
#[derive(Debug, thiserror::Error)]
39+
pub enum GovernanceError {
40+
#[error("You are not authorized to perform this action.")]
41+
Unauthorized,
42+
#[error("The requested proposal was not found.")]
43+
ProposalNotFound,
44+
#[error("Voting period has expired.")]
45+
VotingExpired,
46+
#[error("User has already voted.")]
47+
AlreadyVoted,
48+
#[error("Invalid end time for proposal.")]
49+
InvalidEndTime,
50+
}
4351

4452
pub struct GovernanceDAO {
4553
pub proposals: Vec<Proposal>,
4654
proposal_counter: u64,
55+
user_roles: HashMap<String, Role>,
56+
votes: HashMap<(u64, String), bool>,
57+
quorum: u32,
4758
}
4859

4960
impl GovernanceDAO {
50-
51-
pub fn new() -> Self {
61+
pub fn new(quorum: u32) -> Self {
5262
Self {
5363
proposals: Vec::new(),
5464
proposal_counter: 0,
65+
user_roles: HashMap::new(),
66+
votes: HashMap::new(),
67+
quorum,
68+
}
69+
}
70+
71+
pub fn set_role(&mut self, caller: &UserIdentity, user: UserIdentity) -> Result<(), GovernanceError> {
72+
if !matches!(caller.role, Role::Admin) {
73+
return Err(GovernanceError::Unauthorized);
5574
}
75+
self.user_roles.insert(user.address.clone(), user.role);
76+
Ok(())
5677
}
5778

58-
5979
pub fn submit_proposal(
6080
&mut self,
6181
user: &UserIdentity,
6282
title: String,
6383
description: String,
84+
end_time: u64,
85+
current_time: u64,
6486
) -> Result<(), GovernanceError> {
65-
if matches!(user.role, Role::Tutor | Role::Admin) {
66-
self.proposal_counter += 1;
67-
let proposal = Proposal {
68-
id: self.proposal_counter,
69-
title,
70-
description,
71-
proposer: user.address.clone(),
72-
votes_for: 0,
73-
votes_against: 0,
74-
};
75-
self.proposals.push(proposal);
76-
Ok(())
77-
} else {
78-
Err(GovernanceError::Unauthorized)
87+
if !matches!(user.role, Role::Tutor | Role::Admin) {
88+
return Err(GovernanceError::Unauthorized);
89+
}
90+
if end_time <= current_time {
91+
return Err(GovernanceError::InvalidEndTime);
7992
}
93+
94+
self.proposal_counter += 1;
95+
let proposal = Proposal {
96+
id: self.proposal_counter,
97+
title,
98+
description,
99+
proposer: user.address.clone(),
100+
votes_for: 0,
101+
votes_against: 0,
102+
status: ProposalStatus::Active,
103+
end_time,
104+
};
105+
self.proposals.push(proposal);
106+
Ok(())
80107
}
81108

82-
83109
pub fn vote_on_proposal(
84110
&mut self,
85111
user: &UserIdentity,
86112
proposal_id: u64,
87-
support: bool,
113+
support: bool,
114+
current_time: u64,
88115
) -> Result<(), GovernanceError> {
89-
if matches!(user.role, Role::Student | Role::Tutor) {
90-
let proposal = self.proposals
91-
.iter_mut()
92-
.find(|p| p.id == proposal_id)
93-
.ok_or(GovernanceError::ProposalNotFound)?;
94-
95-
if support {
96-
proposal.votes_for += 1;
97-
} else {
98-
proposal.votes_against += 1;
99-
}
100-
Ok(())
116+
if !matches!(user.role, Role::Student | Role::Tutor) {
117+
return Err(GovernanceError::Unauthorized);
118+
}
119+
120+
let proposal = self.proposals
121+
.iter_mut()
122+
.find(|p| p.id == proposal_id)
123+
.ok_or(GovernanceError::ProposalNotFound)?;
124+
125+
if proposal.status != ProposalStatus::Active {
126+
return Err(GovernanceError::VotingExpired);
127+
}
128+
if current_time > proposal.end_time {
129+
return Err(GovernanceError::VotingExpired);
130+
}
131+
132+
let vote_key = (proposal_id, user.address.clone());
133+
if self.votes.contains_key(&vote_key) {
134+
return Err(GovernanceError::AlreadyVoted);
135+
}
136+
137+
if support {
138+
proposal.votes_for += 1;
101139
} else {
102-
Err(GovernanceError::Unauthorized)
140+
proposal.votes_against += 1;
103141
}
142+
self.votes.insert(vote_key, support);
143+
Ok(())
144+
}
145+
146+
pub fn finalize_proposal(&mut self, proposal_id: u64, current_time: u64) -> Result<(), GovernanceError> {
147+
let proposal = self.proposals
148+
.iter_mut()
149+
.find(|p| p.id == proposal_id)
150+
.ok_or(GovernanceError::ProposalNotFound)?;
151+
152+
if proposal.status != ProposalStatus::Active {
153+
return Err(GovernanceError::VotingExpired);
154+
}
155+
if current_time <= proposal.end_time {
156+
return Err(GovernanceError::VotingExpired);
157+
}
158+
159+
let total_votes = proposal.votes_for + proposal.votes_against;
160+
proposal.status = if total_votes >= self.quorum && proposal.votes_for > proposal.votes_against {
161+
ProposalStatus::Accepted
162+
} else {
163+
ProposalStatus::Rejected
164+
};
165+
Ok(())
104166
}
105167

106-
107168
pub fn list_proposals(&self) -> &Vec<Proposal> {
108169
&self.proposals
109170
}
110171
}
172+

0 commit comments

Comments
 (0)